Add Cloudflare provisioning and deploy tooling
infra/ is OpenTofu (Terraform-compatible) config for the Cloudflare side of the multihost setup: one Pages project, custom domain, and DNS record per language, plus a www.mykiwi.blog -> mykiwi.blog redirect. State is local and gitignored - fine for one person operating this. flake.nix pins opentofu and wrangler the same way hugo/dart-sass already are, in a separate `infra` devShell so writing a post doesn't pull them in, and adds two flake apps: `infra` (forwards to tofu, e.g. `nix run .#infra -- plan`) and `deploy` (builds the site and pushes each language's output to its Cloudflare Pages project via wrangler). The Makefile wraps both plus the everyday nix commands. CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID are read from .env (gitignored) - .env.dist documents the shape without the secret.
e4c5d34 parent: f8c7bc5 added
.env.dist +3 -0 | new file mode 100644 | ||
| @@ -0,0 +1,3 @@ | ||
| 1 | +CLOUDFLARE_ACCOUNT_ID=d8149974c9ab26867fdc9cd579711d7d | |
| 2 | +CLOUDFLARE_API_TOKEN= | |
| 3 | +# https://dash.cloudflare.com/profile/api-tokens | |
| new file mode 100644 | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | +CLOUDFLARE_ACCOUNT_ID=d8149974c9ab26867fdc9cd579711d7d | ||
| 2 | +CLOUDFLARE_API_TOKEN= | ||
| 3 | +# https://dash.cloudflare.com/profile/api-tokens | ||
modified
.gitignore +5 -3 | @@ -1,5 +1,7 @@ | ||
| 1 | -result | |
| 2 | -public/ | |
| 3 | -resources/ | |
| 1 | +.env | |
| 4 | 2 | .hugo_build.lock |
| 3 | + | |
| 5 | 4 | node_modules/ |
| 5 | +public/ | |
| 6 | +resources/ | |
| 7 | +result | |
| @@ -1,5 +1,7 @@ | |||
| 1 | -result | 1 | +.env |
| 2 | -public/ | ||
| 3 | -resources/ | ||
| 4 | .hugo_build.lock | 2 | .hugo_build.lock |
| 3 | + | ||
| 5 | node_modules/ | 4 | node_modules/ |
| 5 | +public/ | ||
| 6 | +resources/ | ||
| 7 | +result | ||
added
Makefile +34 -0 | new file mode 100644 | ||
| @@ -0,0 +1,34 @@ | ||
| 1 | +.DEFAULT_GOAL := help | |
| 2 | + | |
| 3 | +# CLOUDFLARE_API_TOKEN lives in .env (gitignored, copy from .env.dist) - | |
| 4 | +# load it into every recipe's environment, e.g. for infra-plan/infra-apply/deploy. | |
| 5 | +ifneq (,$(wildcard .env)) | |
| 6 | +include .env | |
| 7 | +export | |
| 8 | +endif | |
| 9 | + | |
| 10 | +.PHONY: help dev build serve infra-plan infra-apply deploy clean | |
| 11 | + | |
| 12 | +help: | |
| 13 | + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' Makefile | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-14s\033[0m %s\n", $$1, $$2}' | |
| 14 | + | |
| 15 | +dev: ## Drop into the Hugo dev shell (hugo, dart-sass) | |
| 16 | + nix develop | |
| 17 | + | |
| 18 | +build: ## Build the site (all languages) into ./result | |
| 19 | + nix build | |
| 20 | + | |
| 21 | +serve: ## Run the Hugo dev server with live reload | |
| 22 | + nix develop --command hugo server | |
| 23 | + | |
| 24 | +infra-plan: ## Show what infra-apply would change on Cloudflare | |
| 25 | + nix run .#infra -- plan | |
| 26 | + | |
| 27 | +infra-apply: ## Apply infra/ changes to Cloudflare | |
| 28 | + nix run .#infra -- apply | |
| 29 | + | |
| 30 | +deploy: ## Build the site and push it to Cloudflare Pages (all languages) | |
| 31 | + nix run .#deploy | |
| 32 | + | |
| 33 | +clean: ## Remove build outputs | |
| 34 | + rm -rf result blog/public blog/resources | |
| new file mode 100644 | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | +.DEFAULT_GOAL := help | ||
| 2 | + | ||
| 3 | +# CLOUDFLARE_API_TOKEN lives in .env (gitignored, copy from .env.dist) - | ||
| 4 | +# load it into every recipe's environment, e.g. for infra-plan/infra-apply/deploy. | ||
| 5 | +ifneq (,$(wildcard .env)) | ||
| 6 | +include .env | ||
| 7 | +export | ||
| 8 | +endif | ||
| 9 | + | ||
| 10 | +.PHONY: help dev build serve infra-plan infra-apply deploy clean | ||
| 11 | + | ||
| 12 | +help: | ||
| 13 | + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' Makefile | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-14s\033[0m %s\n", $$1, $$2}' | ||
| 14 | + | ||
| 15 | +dev: ## Drop into the Hugo dev shell (hugo, dart-sass) | ||
| 16 | + nix develop | ||
| 17 | + | ||
| 18 | +build: ## Build the site (all languages) into ./result | ||
| 19 | + nix build | ||
| 20 | + | ||
| 21 | +serve: ## Run the Hugo dev server with live reload | ||
| 22 | + nix develop --command hugo server | ||
| 23 | + | ||
| 24 | +infra-plan: ## Show what infra-apply would change on Cloudflare | ||
| 25 | + nix run .#infra -- plan | ||
| 26 | + | ||
| 27 | +infra-apply: ## Apply infra/ changes to Cloudflare | ||
| 28 | + nix run .#infra -- apply | ||
| 29 | + | ||
| 30 | +deploy: ## Build the site and push it to Cloudflare Pages (all languages) | ||
| 31 | + nix run .#deploy | ||
| 32 | + | ||
| 33 | +clean: ## Remove build outputs | ||
| 34 | + rm -rf result blog/public blog/resources | ||
deleted
blog/content/posts/hello-world.md +0 -9 | deleted file mode 100644 | ||
| @@ -1,9 +0,0 @@ | ||
| 1 | -+++ | |
| 2 | -date = '2026-01-05T09:00:00+01:00' | |
| 3 | -draft = false | |
| 4 | -title = 'Hello World' | |
| 5 | -summary = 'A first post to kick things off.' | |
| 6 | -+++ | |
| 7 | - | |
| 8 | -Welcome to my new blog, built with Hugo. This is a placeholder post used to | |
| 9 | -test the site layout and build pipeline. | |
| deleted file mode 100644 | |||
| @@ -1,9 +0,0 @@ | |||
| 1 | -+++ | ||
| 2 | -date = '2026-01-05T09:00:00+01:00' | ||
| 3 | -draft = false | ||
| 4 | -title = 'Hello World' | ||
| 5 | -summary = 'A first post to kick things off.' | ||
| 6 | -+++ | ||
| 7 | - | ||
| 8 | -Welcome to my new blog, built with Hugo. This is a placeholder post used to | ||
| 9 | -test the site layout and build pipeline. | ||
deleted
blog/content/posts/nix-flakes-for-reproducible-builds.md +0 -27 | deleted file mode 100644 | ||
| @@ -1,27 +0,0 @@ | ||
| 1 | -+++ | |
| 2 | -date = '2026-02-14T10:30:00+01:00' | |
| 3 | -draft = false | |
| 4 | -title = 'Nix Flakes for Reproducible Builds' | |
| 5 | -summary = 'Notes on pinning tool versions with Nix flakes.' | |
| 6 | -+++ | |
| 7 | - | |
| 8 | -Nix flakes make it easy to pin exact versions of dependencies, like the Hugo | |
| 9 | -binary used to build this blog, so every contributor gets identical builds | |
| 10 | -without relying on a system-wide package manager. | |
| 11 | - | |
| 12 | -## Pinning Hugo | |
| 13 | - | |
| 14 | -The `flake.nix` in this repo pins an exact Hugo release through | |
| 15 | -`nixpkgs-multiverse`: | |
| 16 | - | |
| 17 | -```nix | |
| 18 | -hugo = mv.version "hugo" "0.166.0"; | |
| 19 | -``` | |
| 20 | - | |
| 21 | -Running `nix develop` drops that version onto `PATH`, and `nix build` uses | |
| 22 | -the same pin to produce the site. | |
| 23 | - | |
| 24 | -{{% alert "tip" %}} | |
| 25 | -**Tip:** Pin the version once, in one file, and every machine that runs | |
| 26 | -`nix build` gets byte-identical output. | |
| 27 | -{{% /alert %}} | |
| deleted file mode 100644 | |||
| @@ -1,27 +0,0 @@ | |||
| 1 | -+++ | ||
| 2 | -date = '2026-02-14T10:30:00+01:00' | ||
| 3 | -draft = false | ||
| 4 | -title = 'Nix Flakes for Reproducible Builds' | ||
| 5 | -summary = 'Notes on pinning tool versions with Nix flakes.' | ||
| 6 | -+++ | ||
| 7 | - | ||
| 8 | -Nix flakes make it easy to pin exact versions of dependencies, like the Hugo | ||
| 9 | -binary used to build this blog, so every contributor gets identical builds | ||
| 10 | -without relying on a system-wide package manager. | ||
| 11 | - | ||
| 12 | -## Pinning Hugo | ||
| 13 | - | ||
| 14 | -The `flake.nix` in this repo pins an exact Hugo release through | ||
| 15 | -`nixpkgs-multiverse`: | ||
| 16 | - | ||
| 17 | -```nix | ||
| 18 | -hugo = mv.version "hugo" "0.166.0"; | ||
| 19 | -``` | ||
| 20 | - | ||
| 21 | -Running `nix develop` drops that version onto `PATH`, and `nix build` uses | ||
| 22 | -the same pin to produce the site. | ||
| 23 | - | ||
| 24 | -{{% alert "tip" %}} | ||
| 25 | -**Tip:** Pin the version once, in one file, and every machine that runs | ||
| 26 | -`nix build` gets byte-identical output. | ||
| 27 | -{{% /alert %}} | ||
deleted
blog/content/posts/static-sites-in-2026.md +0 -9 | deleted file mode 100644 | ||
| @@ -1,9 +0,0 @@ | ||
| 1 | -+++ | |
| 2 | -date = '2026-03-02T08:15:00+01:00' | |
| 3 | -draft = false | |
| 4 | -title = 'Static Sites in 2026' | |
| 5 | -summary = 'Why static site generators are still a great choice.' | |
| 6 | -+++ | |
| 7 | - | |
| 8 | -Static site generators like Hugo remain a fast, simple option for blogs: | |
| 9 | -no database, no runtime, just plain files served from anywhere. | |
| deleted file mode 100644 | |||
| @@ -1,9 +0,0 @@ | |||
| 1 | -+++ | ||
| 2 | -date = '2026-03-02T08:15:00+01:00' | ||
| 3 | -draft = false | ||
| 4 | -title = 'Static Sites in 2026' | ||
| 5 | -summary = 'Why static site generators are still a great choice.' | ||
| 6 | -+++ | ||
| 7 | - | ||
| 8 | -Static site generators like Hugo remain a fast, simple option for blogs: | ||
| 9 | -no database, no runtime, just plain files served from anywhere. | ||
modified
flake.nix +75 -13 | @@ -10,10 +10,12 @@ | ||
| 10 | 10 | pkgs = pin.tip; |
| 11 | 11 | hugo = pin.version "hugo" "0.166.0"; |
| 12 | 12 | dart-sass = pin.version "dart-sass" "1.104.0"; |
| 13 | + opentofu = pin.version "opentofu" "1.12.6"; | |
| 14 | + wrangler = pin.version "wrangler" "4.129.0"; | |
| 13 | 15 | in |
| 14 | 16 | { |
| 15 | 17 | packages.${system}.default = pkgs.stdenvNoCC.mkDerivation { |
| 16 | - name = "mykiwi-blog"; | |
| 18 | + name = "mykiwi.blog"; | |
| 17 | 19 | src = ./blog; |
| 18 | 20 | nativeBuildInputs = [ hugo dart-sass ]; |
| 19 | 21 | buildPhase = '' |
| @@ -22,20 +24,80 @@ | ||
| 22 | 24 | dontInstall = true; |
| 23 | 25 | }; |
| 24 | 26 | |
| 25 | - devShells.${system}.default = pkgs.mkShell { | |
| 26 | - name = "mykiwi-blog"; | |
| 27 | + devShells.${system} = { | |
| 28 | + default = pkgs.mkShell { | |
| 29 | + name = "mykiwi-blog"; | |
| 27 | 30 | |
| 28 | - packages = [ | |
| 29 | - hugo | |
| 30 | - dart-sass | |
| 31 | - ]; | |
| 31 | + packages = [ | |
| 32 | + hugo | |
| 33 | + dart-sass | |
| 34 | + ]; | |
| 32 | 35 | |
| 33 | - shellHook = '' | |
| 34 | - dim=$'\033[2m' | |
| 35 | - reset=$'\033[0m' | |
| 36 | - printf '%-11s%-9s%s%s%s\n' "Hugo" "$(hugo version | sed -n 's/.*v\([0-9.]*\)+.*/\1/p')" "$dim" "https://github.com/gohugoio/hugo/releases" "$reset" | |
| 37 | - printf '%-11s%-9s%s%s%s\n' "Dart Sass" "$(sass --version)" "$dim" "https://github.com/sass/dart-sass/releases" "$reset" | |
| 38 | - ''; | |
| 36 | + shellHook = '' | |
| 37 | + dim=$'\033[2m' | |
| 38 | + reset=$'\033[0m' | |
| 39 | + printf '%-11s%-9s%s%s%s\n' "Hugo" "$(hugo version | sed -n 's/.*v\([0-9.]*\)+.*/\1/p')" "$dim" "https://github.com/gohugoio/hugo/releases" "$reset" | |
| 40 | + printf '%-11s%-9s%s%s%s\n' "Dart Sass" "$(sass --version)" "$dim" "https://github.com/sass/dart-sass/releases" "$reset" | |
| 41 | + ''; | |
| 42 | + }; | |
| 43 | + | |
| 44 | + # Cloudflare provisioning (infra/) - kept separate from the default | |
| 45 | + # shell so writing a blog post doesn't require pulling in tofu/wrangler. | |
| 46 | + infra = pkgs.mkShell { | |
| 47 | + name = "mykiwi-blog-infra"; | |
| 48 | + | |
| 49 | + packages = [ | |
| 50 | + opentofu | |
| 51 | + wrangler | |
| 52 | + ]; | |
| 53 | + | |
| 54 | + shellHook = '' | |
| 55 | + dim=$'\033[2m' | |
| 56 | + reset=$'\033[0m' | |
| 57 | + printf '%-11s%-9s%s%s%s\n' "OpenTofu" "$(tofu version | sed -n 's/OpenTofu v\([0-9.]*\)/\1/p')" "$dim" "https://github.com/opentofu/opentofu/releases" "$reset" | |
| 58 | + printf '%-11s%-9s%s%s%s\n' "Wrangler" "$(wrangler --version)" "$dim" "https://github.com/cloudflare/workers-sdk/releases" "$reset" | |
| 59 | + ''; | |
| 60 | + }; | |
| 61 | + }; | |
| 62 | + | |
| 63 | + apps.${system} = { | |
| 64 | + # `nix run .#infra -- plan` / `nix run .#infra -- apply` (or any | |
| 65 | + # other tofu subcommand) against infra/, from wherever in the repo | |
| 66 | + # you happen to be. | |
| 67 | + infra = { | |
| 68 | + type = "app"; | |
| 69 | + program = "${pkgs.writeShellApplication { | |
| 70 | + name = "mykiwi-blog-infra"; | |
| 71 | + runtimeInputs = [ opentofu pkgs.git ]; | |
| 72 | + text = '' | |
| 73 | + root="$(git rev-parse --show-toplevel)" | |
| 74 | + tofu -chdir="$root/infra" init | |
| 75 | + tofu -chdir="$root/infra" "$@" | |
| 76 | + ''; | |
| 77 | + }}/bin/mykiwi-blog-infra"; | |
| 78 | + }; | |
| 79 | + | |
| 80 | + # `nix run .#deploy` - builds the site (the same `packages.default` | |
| 81 | + # derivation `nix build` uses) and pushes each language's output | |
| 82 | + # folder to its matching Cloudflare Pages project. Needs | |
| 83 | + # CLOUDFLARE_API_TOKEN, same as `infra`. | |
| 84 | + deploy = { | |
| 85 | + type = "app"; | |
| 86 | + program = "${pkgs.writeShellApplication { | |
| 87 | + name = "mykiwi-blog-deploy"; | |
| 88 | + runtimeInputs = [ wrangler pkgs.git pkgs.nix ]; | |
| 89 | + text = '' | |
| 90 | + root="$(git rev-parse --show-toplevel)" | |
| 91 | + link="$(mktemp -d)/result" | |
| 92 | + nix build "$root#default" --out-link "$link" | |
| 93 | + for lang in en fr pt ja; do | |
| 94 | + project="mykiwi-blog-$lang" | |
| 95 | + echo "==> deploying $lang -> $project" | |
| 96 | + wrangler pages deploy "$link/$lang" --project-name="$project" --branch=main | |
| 97 | + done | |
| 98 | + ''; | |
| 99 | + }}/bin/mykiwi-blog-deploy"; | |
| 100 | + }; | |
| 39 | 101 | }; |
| 40 | 102 | }; |
| 41 | 103 | } |
| @@ -10,10 +10,12 @@ | |||
| 10 | pkgs = pin.tip; | 10 | pkgs = pin.tip; |
| 11 | hugo = pin.version "hugo" "0.166.0"; | 11 | hugo = pin.version "hugo" "0.166.0"; |
| 12 | dart-sass = pin.version "dart-sass" "1.104.0"; | 12 | dart-sass = pin.version "dart-sass" "1.104.0"; |
| 13 | + opentofu = pin.version "opentofu" "1.12.6"; | ||
| 14 | + wrangler = pin.version "wrangler" "4.129.0"; | ||
| 13 | in | 15 | in |
| 14 | { | 16 | { |
| 15 | packages.${system}.default = pkgs.stdenvNoCC.mkDerivation { | 17 | packages.${system}.default = pkgs.stdenvNoCC.mkDerivation { |
| 16 | - name = "mykiwi-blog"; | 18 | + name = "mykiwi.blog"; |
| 17 | src = ./blog; | 19 | src = ./blog; |
| 18 | nativeBuildInputs = [ hugo dart-sass ]; | 20 | nativeBuildInputs = [ hugo dart-sass ]; |
| 19 | buildPhase = '' | 21 | buildPhase = '' |
| @@ -22,20 +24,80 @@ | |||
| 22 | dontInstall = true; | 24 | dontInstall = true; |
| 23 | }; | 25 | }; |
| 24 | 26 | ||
| 25 | - devShells.${system}.default = pkgs.mkShell { | 27 | + devShells.${system} = { |
| 26 | - name = "mykiwi-blog"; | 28 | + default = pkgs.mkShell { |
| 29 | + name = "mykiwi-blog"; | ||
| 27 | 30 | ||
| 28 | - packages = [ | 31 | + packages = [ |
| 29 | - hugo | 32 | + hugo |
| 30 | - dart-sass | 33 | + dart-sass |
| 31 | - ]; | 34 | + ]; |
| 32 | 35 | ||
| 33 | - shellHook = '' | 36 | + shellHook = '' |
| 34 | - dim=$'\033[2m' | 37 | + dim=$'\033[2m' |
| 35 | - reset=$'\033[0m' | 38 | + reset=$'\033[0m' |
| 36 | - printf '%-11s%-9s%s%s%s\n' "Hugo" "$(hugo version | sed -n 's/.*v\([0-9.]*\)+.*/\1/p')" "$dim" "https://github.com/gohugoio/hugo/releases" "$reset" | 39 | + printf '%-11s%-9s%s%s%s\n' "Hugo" "$(hugo version | sed -n 's/.*v\([0-9.]*\)+.*/\1/p')" "$dim" "https://github.com/gohugoio/hugo/releases" "$reset" |
| 37 | - printf '%-11s%-9s%s%s%s\n' "Dart Sass" "$(sass --version)" "$dim" "https://github.com/sass/dart-sass/releases" "$reset" | 40 | + printf '%-11s%-9s%s%s%s\n' "Dart Sass" "$(sass --version)" "$dim" "https://github.com/sass/dart-sass/releases" "$reset" |
| 38 | - ''; | 41 | + ''; |
| 42 | + }; | ||
| 43 | + | ||
| 44 | + # Cloudflare provisioning (infra/) - kept separate from the default | ||
| 45 | + # shell so writing a blog post doesn't require pulling in tofu/wrangler. | ||
| 46 | + infra = pkgs.mkShell { | ||
| 47 | + name = "mykiwi-blog-infra"; | ||
| 48 | + | ||
| 49 | + packages = [ | ||
| 50 | + opentofu | ||
| 51 | + wrangler | ||
| 52 | + ]; | ||
| 53 | + | ||
| 54 | + shellHook = '' | ||
| 55 | + dim=$'\033[2m' | ||
| 56 | + reset=$'\033[0m' | ||
| 57 | + printf '%-11s%-9s%s%s%s\n' "OpenTofu" "$(tofu version | sed -n 's/OpenTofu v\([0-9.]*\)/\1/p')" "$dim" "https://github.com/opentofu/opentofu/releases" "$reset" | ||
| 58 | + printf '%-11s%-9s%s%s%s\n' "Wrangler" "$(wrangler --version)" "$dim" "https://github.com/cloudflare/workers-sdk/releases" "$reset" | ||
| 59 | + ''; | ||
| 60 | + }; | ||
| 61 | + }; | ||
| 62 | + | ||
| 63 | + apps.${system} = { | ||
| 64 | + # `nix run .#infra -- plan` / `nix run .#infra -- apply` (or any | ||
| 65 | + # other tofu subcommand) against infra/, from wherever in the repo | ||
| 66 | + # you happen to be. | ||
| 67 | + infra = { | ||
| 68 | + type = "app"; | ||
| 69 | + program = "${pkgs.writeShellApplication { | ||
| 70 | + name = "mykiwi-blog-infra"; | ||
| 71 | + runtimeInputs = [ opentofu pkgs.git ]; | ||
| 72 | + text = '' | ||
| 73 | + root="$(git rev-parse --show-toplevel)" | ||
| 74 | + tofu -chdir="$root/infra" init | ||
| 75 | + tofu -chdir="$root/infra" "$@" | ||
| 76 | + ''; | ||
| 77 | + }}/bin/mykiwi-blog-infra"; | ||
| 78 | + }; | ||
| 79 | + | ||
| 80 | + # `nix run .#deploy` - builds the site (the same `packages.default` | ||
| 81 | + # derivation `nix build` uses) and pushes each language's output | ||
| 82 | + # folder to its matching Cloudflare Pages project. Needs | ||
| 83 | + # CLOUDFLARE_API_TOKEN, same as `infra`. | ||
| 84 | + deploy = { | ||
| 85 | + type = "app"; | ||
| 86 | + program = "${pkgs.writeShellApplication { | ||
| 87 | + name = "mykiwi-blog-deploy"; | ||
| 88 | + runtimeInputs = [ wrangler pkgs.git pkgs.nix ]; | ||
| 89 | + text = '' | ||
| 90 | + root="$(git rev-parse --show-toplevel)" | ||
| 91 | + link="$(mktemp -d)/result" | ||
| 92 | + nix build "$root#default" --out-link "$link" | ||
| 93 | + for lang in en fr pt ja; do | ||
| 94 | + project="mykiwi-blog-$lang" | ||
| 95 | + echo "==> deploying $lang -> $project" | ||
| 96 | + wrangler pages deploy "$link/$lang" --project-name="$project" --branch=main | ||
| 97 | + done | ||
| 98 | + ''; | ||
| 99 | + }}/bin/mykiwi-blog-deploy"; | ||
| 100 | + }; | ||
| 39 | }; | 101 | }; |
| 40 | }; | 102 | }; |
| 41 | } | 103 | } |
added
infra/.gitignore +10 -0 | new file mode 100644 | ||
| @@ -0,0 +1,10 @@ | ||
| 1 | +.terraform/ | |
| 2 | +*.tfstate | |
| 3 | +*.tfstate.* | |
| 4 | +*.tfvars | |
| 5 | +crash.log | |
| 6 | +crash.*.log | |
| 7 | +override.tf | |
| 8 | +override.tf.json | |
| 9 | +*_override.tf | |
| 10 | +*_override.tf.json | |
| new file mode 100644 | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | +.terraform/ | ||
| 2 | +*.tfstate | ||
| 3 | +*.tfstate.* | ||
| 4 | +*.tfvars | ||
| 5 | +crash.log | ||
| 6 | +crash.*.log | ||
| 7 | +override.tf | ||
| 8 | +override.tf.json | ||
| 9 | +*_override.tf | ||
| 10 | +*_override.tf.json | ||
added
infra/.terraform.lock.hcl +36 -0 | new file mode 100644 | ||
| @@ -0,0 +1,36 @@ | ||
| 1 | +# This file is maintained automatically by "tofu init". | |
| 2 | +# Manual edits may be lost in future updates. | |
| 3 | + | |
| 4 | +provider "registry.opentofu.org/cloudflare/cloudflare" { | |
| 5 | + version = "4.52.9" | |
| 6 | + constraints = "~> 4.44" | |
| 7 | + hashes = [ | |
| 8 | + "h1:LRBYMKjZyuld8kgVhzoceRtdtkht+w8LunrdpgqrL28=", | |
| 9 | + "h1:Q2NeLtrjSTZwHHsMZXKpqOAbxF8DWgqpmH5j+1pUZGw=", | |
| 10 | + "h1:Q4wiux11bDVWRmJScdjp8jLbxMRsj8k3xGtPtY4BPDo=", | |
| 11 | + "h1:VVW111jEbRMTu7hXeGQMmZ/oSTB8LIYhjM5ztyymqAI=", | |
| 12 | + "h1:Z7sTk8/B9XYFAEpTq1ib/WsB2QvpegGwef395tHCUSo=", | |
| 13 | + "h1:eakzS6UsflV4gN/oQWdRPYvvmySKkhgKPyykCfPSVH0=", | |
| 14 | + "h1:eesNMA1JUNBKCnuEvRLW2CT3tNrIejbXJG4A176nA9Q=", | |
| 15 | + "h1:o8txgjqnDDODxQQ6ITwHsvPqHllCbMGnJkjCWNJK5ME=", | |
| 16 | + "h1:rDTPIzab3tbGdtU3NmES08+tRlKG8jygNAFDFbheiuM=", | |
| 17 | + "h1:sIoAt5vaxgFBrst1Esfm7ixQQ/T7/JNPZu0m4bw+Bc4=", | |
| 18 | + "h1:sPCs3KnD/db5TMz2CNgiYix7sRS8+FP92T4ipSYT9vw=", | |
| 19 | + "h1:uXPLsMQUYVJjRGUfmfDQXsKzYr1scvF6PYKj+3f0Nr4=", | |
| 20 | + "h1:vQibC9lgBRQjRQ5KxFH61wd3/q2I40groLPjZ/jiikQ=", | |
| 21 | + "zh:0826cad9b57368f06466ec3e1f766086f44a373288753e144f681ddc0e61ab49", | |
| 22 | + "zh:15bf8d8abdcd202652466f3ef91110f143a7fad13c6173c7180e3dc0dbe9254b", | |
| 23 | + "zh:1a60d1d2e55a17942902b1ccc91e8f6cb150c035843d3eefc074f1291a0e7d5f", | |
| 24 | + "zh:23373a63fe06bbfa90c6c105c08429031719bedafa2458973cbdad32a384b5a7", | |
| 25 | + "zh:256a97b79cedee2575002f77ac3bf18888ce0fc97170ae8a357db406bb0a88e3", | |
| 26 | + "zh:3dd9f93c167cd59491ae77781032491d36a80843a1e44f9ee68b41b3f8e3ab8e", | |
| 27 | + "zh:5182d2d61930dd83584d8feb56102614f80ddeec532b939b58085ad74b796a6e", | |
| 28 | + "zh:5e3ca1888ba3b3bd8abd0f7dfcdd82fb5a8819affee899ed7fd891a33c17e0cb", | |
| 29 | + "zh:7a3ce2119e39fb3a90a6a459a26863851309dbc92e0735336cbc98692539cb4d", | |
| 30 | + "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", | |
| 31 | + "zh:c095dc680a646cc078c10bb0551e2ea8f08e7abdbcbc58cfd332c56b58788631", | |
| 32 | + "zh:d2400e010e069c19916c99136e200cf873eba6db2b18d3eb9541b38eb1f565fa", | |
| 33 | + "zh:dbc2f19f09aaefbd69334005bca2bcb0c9b690de1ed76f7ba97ce8e59a6e85df", | |
| 34 | + "zh:e685225f432da3e23b8717848de8c0ad0e22363450b2ac92cd24a56739ab5e9b", | |
| 35 | + ] | |
| 36 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | +# This file is maintained automatically by "tofu init". | ||
| 2 | +# Manual edits may be lost in future updates. | ||
| 3 | + | ||
| 4 | +provider "registry.opentofu.org/cloudflare/cloudflare" { | ||
| 5 | + version = "4.52.9" | ||
| 6 | + constraints = "~> 4.44" | ||
| 7 | + hashes = [ | ||
| 8 | + "h1:LRBYMKjZyuld8kgVhzoceRtdtkht+w8LunrdpgqrL28=", | ||
| 9 | + "h1:Q2NeLtrjSTZwHHsMZXKpqOAbxF8DWgqpmH5j+1pUZGw=", | ||
| 10 | + "h1:Q4wiux11bDVWRmJScdjp8jLbxMRsj8k3xGtPtY4BPDo=", | ||
| 11 | + "h1:VVW111jEbRMTu7hXeGQMmZ/oSTB8LIYhjM5ztyymqAI=", | ||
| 12 | + "h1:Z7sTk8/B9XYFAEpTq1ib/WsB2QvpegGwef395tHCUSo=", | ||
| 13 | + "h1:eakzS6UsflV4gN/oQWdRPYvvmySKkhgKPyykCfPSVH0=", | ||
| 14 | + "h1:eesNMA1JUNBKCnuEvRLW2CT3tNrIejbXJG4A176nA9Q=", | ||
| 15 | + "h1:o8txgjqnDDODxQQ6ITwHsvPqHllCbMGnJkjCWNJK5ME=", | ||
| 16 | + "h1:rDTPIzab3tbGdtU3NmES08+tRlKG8jygNAFDFbheiuM=", | ||
| 17 | + "h1:sIoAt5vaxgFBrst1Esfm7ixQQ/T7/JNPZu0m4bw+Bc4=", | ||
| 18 | + "h1:sPCs3KnD/db5TMz2CNgiYix7sRS8+FP92T4ipSYT9vw=", | ||
| 19 | + "h1:uXPLsMQUYVJjRGUfmfDQXsKzYr1scvF6PYKj+3f0Nr4=", | ||
| 20 | + "h1:vQibC9lgBRQjRQ5KxFH61wd3/q2I40groLPjZ/jiikQ=", | ||
| 21 | + "zh:0826cad9b57368f06466ec3e1f766086f44a373288753e144f681ddc0e61ab49", | ||
| 22 | + "zh:15bf8d8abdcd202652466f3ef91110f143a7fad13c6173c7180e3dc0dbe9254b", | ||
| 23 | + "zh:1a60d1d2e55a17942902b1ccc91e8f6cb150c035843d3eefc074f1291a0e7d5f", | ||
| 24 | + "zh:23373a63fe06bbfa90c6c105c08429031719bedafa2458973cbdad32a384b5a7", | ||
| 25 | + "zh:256a97b79cedee2575002f77ac3bf18888ce0fc97170ae8a357db406bb0a88e3", | ||
| 26 | + "zh:3dd9f93c167cd59491ae77781032491d36a80843a1e44f9ee68b41b3f8e3ab8e", | ||
| 27 | + "zh:5182d2d61930dd83584d8feb56102614f80ddeec532b939b58085ad74b796a6e", | ||
| 28 | + "zh:5e3ca1888ba3b3bd8abd0f7dfcdd82fb5a8819affee899ed7fd891a33c17e0cb", | ||
| 29 | + "zh:7a3ce2119e39fb3a90a6a459a26863851309dbc92e0735336cbc98692539cb4d", | ||
| 30 | + "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", | ||
| 31 | + "zh:c095dc680a646cc078c10bb0551e2ea8f08e7abdbcbc58cfd332c56b58788631", | ||
| 32 | + "zh:d2400e010e069c19916c99136e200cf873eba6db2b18d3eb9541b38eb1f565fa", | ||
| 33 | + "zh:dbc2f19f09aaefbd69334005bca2bcb0c9b690de1ed76f7ba97ce8e59a6e85df", | ||
| 34 | + "zh:e685225f432da3e23b8717848de8c0ad0e22363450b2ac92cd24a56739ab5e9b", | ||
| 35 | + ] | ||
| 36 | +} | ||
added
infra/README.md +46 -0 | new file mode 100644 | ||
| @@ -0,0 +1,46 @@ | ||
| 1 | +# infra | |
| 2 | + | |
| 3 | +OpenTofu config for the Cloudflare side of the multihost setup in | |
| 4 | +`blog/hugo.toml`: one Cloudflare Pages project, custom domain, and DNS | |
| 5 | +record per language (`mykiwi.blog`, `fr.mykiwi.blog`, `pt.mykiwi.blog`, | |
| 6 | +`ja.mykiwi.blog`). It assumes `mykiwi.blog` is already a zone in your | |
| 7 | +Cloudflare account - this only manages what's *inside* that zone. | |
| 8 | + | |
| 9 | +Deploys (`wrangler pages deploy public/<lang> --project-name=...`) are out | |
| 10 | +of scope here; this just provisions the projects and domains deploys land | |
| 11 | +on. | |
| 12 | + | |
| 13 | +## Setup | |
| 14 | + | |
| 15 | +Create a token at https://dash.cloudflare.com/profile/api-tokens (Custom | |
| 16 | +Token, scoped to the `mykiwi.blog` zone) with `Zone:DNS:Edit`, | |
| 17 | +`Zone:Zone:Read`, and `Account:Cloudflare Pages:Edit`, then: | |
| 18 | + | |
| 19 | +```sh | |
| 20 | +export CLOUDFLARE_API_TOKEN=... | |
| 21 | +``` | |
| 22 | + | |
| 23 | +`cloudflare_account_id` is hardcoded in `variables.tf` (it's not a secret - | |
| 24 | +just an identifier) - override it with `-var` if you're ever applying | |
| 25 | +against a different account. | |
| 26 | + | |
| 27 | +## Usage | |
| 28 | + | |
| 29 | +From the repo root: | |
| 30 | + | |
| 31 | +```sh | |
| 32 | +make infra-plan | |
| 33 | +make infra-apply | |
| 34 | +``` | |
| 35 | + | |
| 36 | +Or directly, from this directory (`nix develop .#infra` for `tofu`): | |
| 37 | + | |
| 38 | +```sh | |
| 39 | +tofu init | |
| 40 | +tofu plan | |
| 41 | +tofu apply | |
| 42 | +``` | |
| 43 | + | |
| 44 | +State is local (`terraform.tfstate`, gitignored) - fine for a single person | |
| 45 | +operating this. If that ever changes, move the backend to something shared | |
| 46 | +(e.g. an R2 bucket) before a second person touches this. | |
| new file mode 100644 | |||
| @@ -0,0 +1,46 @@ | |||
| 1 | +# infra | ||
| 2 | + | ||
| 3 | +OpenTofu config for the Cloudflare side of the multihost setup in | ||
| 4 | +`blog/hugo.toml`: one Cloudflare Pages project, custom domain, and DNS | ||
| 5 | +record per language (`mykiwi.blog`, `fr.mykiwi.blog`, `pt.mykiwi.blog`, | ||
| 6 | +`ja.mykiwi.blog`). It assumes `mykiwi.blog` is already a zone in your | ||
| 7 | +Cloudflare account - this only manages what's *inside* that zone. | ||
| 8 | + | ||
| 9 | +Deploys (`wrangler pages deploy public/<lang> --project-name=...`) are out | ||
| 10 | +of scope here; this just provisions the projects and domains deploys land | ||
| 11 | +on. | ||
| 12 | + | ||
| 13 | +## Setup | ||
| 14 | + | ||
| 15 | +Create a token at https://dash.cloudflare.com/profile/api-tokens (Custom | ||
| 16 | +Token, scoped to the `mykiwi.blog` zone) with `Zone:DNS:Edit`, | ||
| 17 | +`Zone:Zone:Read`, and `Account:Cloudflare Pages:Edit`, then: | ||
| 18 | + | ||
| 19 | +```sh | ||
| 20 | +export CLOUDFLARE_API_TOKEN=... | ||
| 21 | +``` | ||
| 22 | + | ||
| 23 | +`cloudflare_account_id` is hardcoded in `variables.tf` (it's not a secret - | ||
| 24 | +just an identifier) - override it with `-var` if you're ever applying | ||
| 25 | +against a different account. | ||
| 26 | + | ||
| 27 | +## Usage | ||
| 28 | + | ||
| 29 | +From the repo root: | ||
| 30 | + | ||
| 31 | +```sh | ||
| 32 | +make infra-plan | ||
| 33 | +make infra-apply | ||
| 34 | +``` | ||
| 35 | + | ||
| 36 | +Or directly, from this directory (`nix develop .#infra` for `tofu`): | ||
| 37 | + | ||
| 38 | +```sh | ||
| 39 | +tofu init | ||
| 40 | +tofu plan | ||
| 41 | +tofu apply | ||
| 42 | +``` | ||
| 43 | + | ||
| 44 | +State is local (`terraform.tfstate`, gitignored) - fine for a single person | ||
| 45 | +operating this. If that ever changes, move the backend to something shared | ||
| 46 | +(e.g. an R2 bucket) before a second person touches this. | ||
added
infra/main.tf +69 -0 | new file mode 100644 | ||
| @@ -0,0 +1,69 @@ | ||
| 1 | +# One Cloudflare Pages project + custom domain + DNS record per language, | |
| 2 | +# matching blog/hugo.toml's multihost [languages.*] baseURLs: each language | |
| 3 | +# is a fully separate site (public/<lang>/ from the Hugo build) deployed to | |
| 4 | +# its own subdomain, with English at the zone apex. | |
| 5 | +locals { | |
| 6 | + languages = { | |
| 7 | + en = { hostname = var.zone_name, project_name = "mykiwi-blog-en" } | |
| 8 | + fr = { hostname = "fr.${var.zone_name}", project_name = "mykiwi-blog-fr" } | |
| 9 | + pt = { hostname = "pt.${var.zone_name}", project_name = "mykiwi-blog-pt" } | |
| 10 | + ja = { hostname = "ja.${var.zone_name}", project_name = "mykiwi-blog-ja" } | |
| 11 | + } | |
| 12 | +} | |
| 13 | + | |
| 14 | +data "cloudflare_zone" "this" { | |
| 15 | + name = var.zone_name | |
| 16 | +} | |
| 17 | + | |
| 18 | +# Direct-upload projects (no `source` block, i.e. no git integration) - | |
| 19 | +# deploys happen out-of-band, e.g. `wrangler pages deploy public/en | |
| 20 | +# --project-name=mykiwi-blog-en` from CI, since this repo lives on | |
| 21 | +# tangled.sh rather than GitHub. | |
| 22 | +resource "cloudflare_pages_project" "site" { | |
| 23 | + for_each = local.languages | |
| 24 | + | |
| 25 | + account_id = var.cloudflare_account_id | |
| 26 | + name = each.value.project_name | |
| 27 | + production_branch = "main" | |
| 28 | +} | |
| 29 | + | |
| 30 | +resource "cloudflare_pages_domain" "site" { | |
| 31 | + for_each = local.languages | |
| 32 | + | |
| 33 | + account_id = var.cloudflare_account_id | |
| 34 | + project_name = cloudflare_pages_project.site[each.key].name | |
| 35 | + domain = each.value.hostname | |
| 36 | +} | |
| 37 | + | |
| 38 | +resource "cloudflare_record" "site" { | |
| 39 | + for_each = local.languages | |
| 40 | + | |
| 41 | + zone_id = data.cloudflare_zone.this.id | |
| 42 | + name = each.key == "en" ? "@" : each.key | |
| 43 | + type = "CNAME" | |
| 44 | + content = cloudflare_pages_project.site[each.key].subdomain | |
| 45 | + proxied = true | |
| 46 | +} | |
| 47 | + | |
| 48 | +# www.mykiwi.blog -> mykiwi.blog (English apex), not its own language - just | |
| 49 | +# the one common typo/habit redirect, not a 5th site. | |
| 50 | +resource "cloudflare_record" "www" { | |
| 51 | + zone_id = data.cloudflare_zone.this.id | |
| 52 | + name = "www" | |
| 53 | + type = "CNAME" | |
| 54 | + content = var.zone_name | |
| 55 | + proxied = true | |
| 56 | +} | |
| 57 | + | |
| 58 | +resource "cloudflare_page_rule" "www_redirect" { | |
| 59 | + zone_id = data.cloudflare_zone.this.id | |
| 60 | + target = "www.${var.zone_name}/*" | |
| 61 | + priority = 1 | |
| 62 | + | |
| 63 | + actions { | |
| 64 | + forwarding_url { | |
| 65 | + url = "https://${var.zone_name}/$1" | |
| 66 | + status_code = 301 | |
| 67 | + } | |
| 68 | + } | |
| 69 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,69 @@ | |||
| 1 | +# One Cloudflare Pages project + custom domain + DNS record per language, | ||
| 2 | +# matching blog/hugo.toml's multihost [languages.*] baseURLs: each language | ||
| 3 | +# is a fully separate site (public/<lang>/ from the Hugo build) deployed to | ||
| 4 | +# its own subdomain, with English at the zone apex. | ||
| 5 | +locals { | ||
| 6 | + languages = { | ||
| 7 | + en = { hostname = var.zone_name, project_name = "mykiwi-blog-en" } | ||
| 8 | + fr = { hostname = "fr.${var.zone_name}", project_name = "mykiwi-blog-fr" } | ||
| 9 | + pt = { hostname = "pt.${var.zone_name}", project_name = "mykiwi-blog-pt" } | ||
| 10 | + ja = { hostname = "ja.${var.zone_name}", project_name = "mykiwi-blog-ja" } | ||
| 11 | + } | ||
| 12 | +} | ||
| 13 | + | ||
| 14 | +data "cloudflare_zone" "this" { | ||
| 15 | + name = var.zone_name | ||
| 16 | +} | ||
| 17 | + | ||
| 18 | +# Direct-upload projects (no `source` block, i.e. no git integration) - | ||
| 19 | +# deploys happen out-of-band, e.g. `wrangler pages deploy public/en | ||
| 20 | +# --project-name=mykiwi-blog-en` from CI, since this repo lives on | ||
| 21 | +# tangled.sh rather than GitHub. | ||
| 22 | +resource "cloudflare_pages_project" "site" { | ||
| 23 | + for_each = local.languages | ||
| 24 | + | ||
| 25 | + account_id = var.cloudflare_account_id | ||
| 26 | + name = each.value.project_name | ||
| 27 | + production_branch = "main" | ||
| 28 | +} | ||
| 29 | + | ||
| 30 | +resource "cloudflare_pages_domain" "site" { | ||
| 31 | + for_each = local.languages | ||
| 32 | + | ||
| 33 | + account_id = var.cloudflare_account_id | ||
| 34 | + project_name = cloudflare_pages_project.site[each.key].name | ||
| 35 | + domain = each.value.hostname | ||
| 36 | +} | ||
| 37 | + | ||
| 38 | +resource "cloudflare_record" "site" { | ||
| 39 | + for_each = local.languages | ||
| 40 | + | ||
| 41 | + zone_id = data.cloudflare_zone.this.id | ||
| 42 | + name = each.key == "en" ? "@" : each.key | ||
| 43 | + type = "CNAME" | ||
| 44 | + content = cloudflare_pages_project.site[each.key].subdomain | ||
| 45 | + proxied = true | ||
| 46 | +} | ||
| 47 | + | ||
| 48 | +# www.mykiwi.blog -> mykiwi.blog (English apex), not its own language - just | ||
| 49 | +# the one common typo/habit redirect, not a 5th site. | ||
| 50 | +resource "cloudflare_record" "www" { | ||
| 51 | + zone_id = data.cloudflare_zone.this.id | ||
| 52 | + name = "www" | ||
| 53 | + type = "CNAME" | ||
| 54 | + content = var.zone_name | ||
| 55 | + proxied = true | ||
| 56 | +} | ||
| 57 | + | ||
| 58 | +resource "cloudflare_page_rule" "www_redirect" { | ||
| 59 | + zone_id = data.cloudflare_zone.this.id | ||
| 60 | + target = "www.${var.zone_name}/*" | ||
| 61 | + priority = 1 | ||
| 62 | + | ||
| 63 | + actions { | ||
| 64 | + forwarding_url { | ||
| 65 | + url = "https://${var.zone_name}/$1" | ||
| 66 | + status_code = 301 | ||
| 67 | + } | ||
| 68 | + } | ||
| 69 | +} | ||
added
infra/outputs.tf +9 -0 | new file mode 100644 | ||
| @@ -0,0 +1,9 @@ | ||
| 1 | +output "pages_subdomains" { | |
| 2 | + description = "The <project>.pages.dev address behind each custom domain - useful for deploying, e.g. `wrangler pages deploy public/en --project-name=mykiwi-blog-en`." | |
| 3 | + value = { for k, v in cloudflare_pages_project.site : k => v.subdomain } | |
| 4 | +} | |
| 5 | + | |
| 6 | +output "hostnames" { | |
| 7 | + description = "The final public hostname for each language." | |
| 8 | + value = { for k, v in local.languages : k => v.hostname } | |
| 9 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | +output "pages_subdomains" { | ||
| 2 | + description = "The <project>.pages.dev address behind each custom domain - useful for deploying, e.g. `wrangler pages deploy public/en --project-name=mykiwi-blog-en`." | ||
| 3 | + value = { for k, v in cloudflare_pages_project.site : k => v.subdomain } | ||
| 4 | +} | ||
| 5 | + | ||
| 6 | +output "hostnames" { | ||
| 7 | + description = "The final public hostname for each language." | ||
| 8 | + value = { for k, v in local.languages : k => v.hostname } | ||
| 9 | +} | ||
added
infra/variables.tf +13 -0 | new file mode 100644 | ||
| @@ -0,0 +1,13 @@ | ||
| 1 | +variable "cloudflare_account_id" { | |
| 2 | + # Not a secret - it's just an identifier (visible in any Cloudflare | |
| 3 | + # dashboard URL for this account), meaningless without the API token. | |
| 4 | + description = "Cloudflare account ID that owns the zone and will own the Pages projects." | |
| 5 | + type = string | |
| 6 | + default = "d8149974c9ab26867fdc9cd579711d7d" | |
| 7 | +} | |
| 8 | + | |
| 9 | +variable "zone_name" { | |
| 10 | + description = "Root domain already managed in Cloudflare (must exist as a zone before applying)." | |
| 11 | + type = string | |
| 12 | + default = "mykiwi.blog" | |
| 13 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | +variable "cloudflare_account_id" { | ||
| 2 | + # Not a secret - it's just an identifier (visible in any Cloudflare | ||
| 3 | + # dashboard URL for this account), meaningless without the API token. | ||
| 4 | + description = "Cloudflare account ID that owns the zone and will own the Pages projects." | ||
| 5 | + type = string | ||
| 6 | + default = "d8149974c9ab26867fdc9cd579711d7d" | ||
| 7 | +} | ||
| 8 | + | ||
| 9 | +variable "zone_name" { | ||
| 10 | + description = "Root domain already managed in Cloudflare (must exist as a zone before applying)." | ||
| 11 | + type = string | ||
| 12 | + default = "mykiwi.blog" | ||
| 13 | +} | ||
added
infra/versions.tf +15 -0 | new file mode 100644 | ||
| @@ -0,0 +1,15 @@ | ||
| 1 | +terraform { | |
| 2 | + required_version = ">= 1.6.0" | |
| 3 | + | |
| 4 | + required_providers { | |
| 5 | + cloudflare = { | |
| 6 | + source = "cloudflare/cloudflare" | |
| 7 | + version = "~> 4.44" | |
| 8 | + } | |
| 9 | + } | |
| 10 | +} | |
| 11 | + | |
| 12 | +# CLOUDFLARE_API_TOKEN is read from the environment automatically by the | |
| 13 | +# provider - never pass the token in as a variable or tfvars value, since | |
| 14 | +# that would land it in the (unencrypted, locally-stored) state file. | |
| 15 | +provider "cloudflare" {} | |
| new file mode 100644 | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | +terraform { | ||
| 2 | + required_version = ">= 1.6.0" | ||
| 3 | + | ||
| 4 | + required_providers { | ||
| 5 | + cloudflare = { | ||
| 6 | + source = "cloudflare/cloudflare" | ||
| 7 | + version = "~> 4.44" | ||
| 8 | + } | ||
| 9 | + } | ||
| 10 | +} | ||
| 11 | + | ||
| 12 | +# CLOUDFLARE_API_TOKEN is read from the environment automatically by the | ||
| 13 | +# provider - never pass the token in as a variable or tfvars value, since | ||
| 14 | +# that would land it in the (unencrypted, locally-stored) state file. | ||
| 15 | +provider "cloudflare" {} | ||