From 75b289de9799cbdc0fbab01044489b7a024a5984 Mon Sep 17 00:00:00 2001 From: Wiktoria Van Harneveldt Date: Thu, 14 May 2026 15:58:07 +0200 Subject: [PATCH 1/4] chore: update .gitignore to exclude pnpm store directory --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2124a53..83dec02 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ node_modules/ .env .env.local .env.*.local +.pnpm-store/ # Terraform .terraform/ From 2ca8881c74e6a4284bc6fd2c83406a7554162973 Mon Sep 17 00:00:00 2001 From: Wiktoria Van Harneveldt Date: Thu, 14 May 2026 15:58:38 +0200 Subject: [PATCH 2/4] feat: add Terraform configuration for Netcup DNS and Cloudflare Pages integration --- terraform/cloudflare.tf | 16 ++++++++++++ terraform/main.tf | 31 +++++++++++++++++++++++ terraform/netcup.tf | 18 +++++++++++++ terraform/variables.tf | 56 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 121 insertions(+) create mode 100644 terraform/cloudflare.tf create mode 100644 terraform/main.tf create mode 100644 terraform/netcup.tf create mode 100644 terraform/variables.tf diff --git a/terraform/cloudflare.tf b/terraform/cloudflare.tf new file mode 100644 index 0000000..faced5a --- /dev/null +++ b/terraform/cloudflare.tf @@ -0,0 +1,16 @@ +provider "cloudflare" { + api_token = var.cloudflare_api_token +} + +resource "cloudflare_pages_domain" "webring" { + depends_on = [netcup-ccp_dns_record.webring] + + account_id = var.cloudflare_account_id + project_name = var.pages_project_name + name = local.domain_name +} + +output "pages_domain_status" { + description = "Cloudflare Pages custom-domain status (e.g. pending until DNS is visible)." + value = cloudflare_pages_domain.webring.status +} diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..0f9eb82 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,31 @@ +# ----------------------------------------------------------------------------- +# Netcup DNS + Cloudflare Pages +# +# Netcup: CNAME webring → Pages host. Cloudflare: register custom domain on Pages. +# See netcup.tf and cloudflare.tf. Variables: variables.tf +# ----------------------------------------------------------------------------- + +terraform { + required_version = ">= 1.5.0" + + required_providers { + cloudflare = { + source = "cloudflare/cloudflare" + version = "~> 4.0" + } + netcup-ccp = { + source = "rincedd/netcup-ccp" + version = "~> 0.0.1" + } + } +} + +locals { + domain_name = "${var.subdomain_name}.${var.dns_domain}" + pages_cname_target = var.pages_cname_target != "" ? var.pages_cname_target : "${var.pages_project_name}.pages.dev" +} + +output "webring_domain_name" { + description = "Hostname: Netcup CNAME + Pages custom domain." + value = local.domain_name +} diff --git a/terraform/netcup.tf b/terraform/netcup.tf new file mode 100644 index 0000000..283d205 --- /dev/null +++ b/terraform/netcup.tf @@ -0,0 +1,18 @@ +provider "netcup-ccp" { + customer_number = var.netcup_customer_number + ccp_api_key = var.netcup_ccp_api_key + ccp_api_password = var.netcup_ccp_api_password +} + +resource "netcup-ccp_dns_record" "webring" { + domain_name = var.dns_domain + name = var.subdomain_name + type = "CNAME" + value = local.pages_cname_target + priority = "0" +} + +output "netcup_cname_target" { + description = "Value of the CNAME record in Netcup." + value = local.pages_cname_target +} diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..8538791 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,56 @@ +# All input variables for this module (Netcup + Cloudflare Pages). + +# --- Netcup (DNS at Netcup CCP) --- + +variable "netcup_customer_number" { + description = "Netcup customer number (same as CCP login)." + type = string +} + +variable "netcup_ccp_api_key" { + description = "Netcup CCP API key (CCP → Stammdaten → API)." + type = string + sensitive = true +} + +variable "netcup_ccp_api_password" { + description = "Netcup CCP API password." + type = string + sensitive = true +} + +variable "dns_domain" { + description = "Apex domain whose DNS zone Netcup hosts (e.g. webdev-webdesign.com)." + type = string + default = "webdev-webdesign.com" +} + +variable "subdomain_name" { + description = "Hostname label managed in Netcup (webring → webring.webdev-webdesign.com)." + type = string + default = "webring" +} + +# --- Cloudflare Pages (hostname on the project) --- + +variable "cloudflare_api_token" { + description = "API token with Account → Cloudflare Pages → Edit (and Pages Read). Not used for Netcup DNS." + type = string + sensitive = true +} + +variable "cloudflare_account_id" { + description = "Cloudflare account ID (Workers & Pages overview, or URL in dashboard)." + type = string +} + +variable "pages_project_name" { + description = "Pages project name as shown in the Cloudflare dashboard (slug)." + type = string +} + +variable "pages_cname_target" { + description = "CNAME target in Netcup. Leave empty to use \".pages.dev\"." + type = string + default = "" +} \ No newline at end of file From 00be8e820ba05a8b2a28e7c9aad78acb0218c363 Mon Sep 17 00:00:00 2001 From: Wiktoria Van Harneveldt Date: Mon, 8 Jun 2026 21:38:29 +0200 Subject: [PATCH 3/4] ci(terraform): add GitHub Actions workflow for plan and apply Populate prod.tfvars from repository secrets and variables at runtime, run validate and plan on PRs and main pushes, and apply only via workflow_dispatch to avoid unintended production changes. Co-authored-by: Cursor --- .github/workflows/terraform.yml | 114 ++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 .github/workflows/terraform.yml diff --git a/.github/workflows/terraform.yml b/.github/workflows/terraform.yml new file mode 100644 index 0000000..b573310 --- /dev/null +++ b/.github/workflows/terraform.yml @@ -0,0 +1,114 @@ +# Populates terraform/prod.tfvars at job runtime from GitHub Secrets / Variables, +# then runs Terraform (plan on PR/push; apply only when you run the workflow manually). +# +# Configure in the repo: +# Settings → Secrets and variables → Actions +# +# Secrets (required for plan/apply): +# TF_NETCUP_CCP_API_KEY +# TF_NETCUP_CCP_API_PASSWORD +# TF_CLOUDFLARE_API_TOKEN +# +# Variables (optional; sensible defaults are applied in the "Write prod.tfvars" step): +# TF_DNS_DOMAIN +# TF_SUBDOMAIN_NAME +# TF_PAGES_PROJECT_NAME +# TF_PAGES_CNAME_TARGET (leave unset for ".pages.dev") +# TF_NETCUP_CUSTOMER_NUMBER +# TF_CLOUDFLARE_ACCOUNT_ID + +name: Terraform + +on: + pull_request: + paths: + - "terraform/**" + - ".github/workflows/terraform.yml" + push: + branches: + - main + paths: + - "terraform/**" + - ".github/workflows/terraform.yml" + workflow_dispatch: + +concurrency: + group: terraform-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +defaults: + run: + working-directory: terraform + +jobs: + terraform: + runs-on: ubuntu-latest + # Fork PRs do not receive repository secrets; skip to avoid failing on missing credentials. + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: "1.7.5" + terraform_wrapper: false + + - name: Write prod.tfvars + env: + DNS_DOMAIN: ${{ vars.TF_DNS_DOMAIN }} + SUBDOMAIN_NAME: ${{ vars.TF_SUBDOMAIN_NAME }} + PAGES_PROJECT_NAME: ${{ vars.TF_PAGES_PROJECT_NAME }} + PAGES_CNAME_TARGET: ${{ vars.TF_PAGES_CNAME_TARGET }} + NETCUP_CUSTOMER_NUMBER: ${{ vars.TF_NETCUP_CUSTOMER_NUMBER }} + CLOUDFLARE_ACCOUNT_ID: ${{ vars.TF_CLOUDFLARE_ACCOUNT_ID }} + NETCUP_CCP_API_KEY: ${{ secrets.TF_NETCUP_CCP_API_KEY }} + NETCUP_CCP_API_PASSWORD: ${{ secrets.TF_NETCUP_CCP_API_PASSWORD }} + CLOUDFLARE_API_TOKEN: ${{ secrets.TF_CLOUDFLARE_API_TOKEN }} + run: | + set -euo pipefail + : "${NETCUP_CCP_API_KEY:?Set secret TF_NETCUP_CCP_API_KEY}" + : "${NETCUP_CCP_API_PASSWORD:?Set secret TF_NETCUP_CCP_API_PASSWORD}" + : "${CLOUDFLARE_API_TOKEN:?Set secret TF_CLOUDFLARE_API_TOKEN}" + : "${NETCUP_CUSTOMER_NUMBER:?Set variable TF_NETCUP_CUSTOMER_NUMBER}" + : "${CLOUDFLARE_ACCOUNT_ID:?Set variable TF_CLOUDFLARE_ACCOUNT_ID}" + : "${PAGES_PROJECT_NAME:?Set variable TF_PAGES_PROJECT_NAME}" + + dns_domain="${DNS_DOMAIN:-webdev-webdesign.com}" + subdomain_name="${SUBDOMAIN_NAME:-webring}" + pages_cname_target="${PAGES_CNAME_TARGET:-}" + + umask 077 + cat > prod.tfvars < Date: Mon, 8 Jun 2026 21:38:32 +0200 Subject: [PATCH 4/4] chore(terraform): ignore tfvars files and add configuration example Keep prod.tfvars and terraform.tfvars out of version control and document how to configure variables locally or via the GitHub Actions workflow. Co-authored-by: Cursor --- .gitignore | 2 ++ terraform/terraform.tfvars.example | 14 ++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 terraform/terraform.tfvars.example diff --git a/.gitignore b/.gitignore index 83dec02..72cc694 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ node_modules/ crash.log override.tf terraform.tfvars +terraform/terraform.tfvars +terraform/prod.tfvars diff --git a/terraform/terraform.tfvars.example b/terraform/terraform.tfvars.example new file mode 100644 index 0000000..5e8fb93 --- /dev/null +++ b/terraform/terraform.tfvars.example @@ -0,0 +1,14 @@ +dns_domain = "webdev-webdesign.com" +subdomain_name = "webring" +pages_project_name = "your-pages-project-name" + +# Optional: override CNAME target (default is ".pages.dev") +# pages_cname_target = "your-pages-project-name.pages.dev" + +netcup_customer_number = "123456" +cloudflare_account_id = "paste-account-id-from-dashboard" + +# Secrets — locally: export TF_VAR_netcup_ccp_api_key / … or put them in a gitignored tfvars file. +# +# CI (GitHub Actions): do not commit prod.tfvars. The workflow .github/workflows/terraform.yml +# writes terraform/prod.tfvars from repository Secrets / Variables (see comments in that file).