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
114 changes: 114 additions & 0 deletions .github/workflows/terraform.yml
Original file line number Diff line number Diff line change
@@ -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 "<project>.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 <<EOF
dns_domain = "${dns_domain}"
subdomain_name = "${subdomain_name}"
pages_project_name = "${PAGES_PROJECT_NAME}"
pages_cname_target = "${pages_cname_target}"
netcup_customer_number = "${NETCUP_CUSTOMER_NUMBER}"
cloudflare_account_id = "${CLOUDFLARE_ACCOUNT_ID}"
netcup_ccp_api_key = "${NETCUP_CCP_API_KEY}"
netcup_ccp_api_password = "${NETCUP_CCP_API_PASSWORD}"
cloudflare_api_token = "${CLOUDFLARE_API_TOKEN}"
EOF
chmod 600 prod.tfvars

- name: Terraform fmt
run: terraform fmt -check

- name: Terraform init
run: terraform init -input=false

- name: Terraform validate
run: terraform validate

- name: Terraform plan
run: terraform plan -input=false -var-file=prod.tfvars -no-color

- name: Terraform apply
if: github.event_name == 'workflow_dispatch'
run: terraform apply -input=false -auto-approve -var-file=prod.tfvars -no-color
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ node_modules/
.env
.env.local
.env.*.local
.pnpm-store/

# Terraform
.terraform/
Expand All @@ -14,3 +15,5 @@ node_modules/
crash.log
override.tf
terraform.tfvars
terraform/terraform.tfvars
terraform/prod.tfvars
16 changes: 16 additions & 0 deletions terraform/cloudflare.tf
Original file line number Diff line number Diff line change
@@ -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
}
31 changes: 31 additions & 0 deletions terraform/main.tf
Original file line number Diff line number Diff line change
@@ -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
}
18 changes: 18 additions & 0 deletions terraform/netcup.tf
Original file line number Diff line number Diff line change
@@ -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
}
14 changes: 14 additions & 0 deletions terraform/terraform.tfvars.example
Original file line number Diff line number Diff line change
@@ -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_project_name>.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).
56 changes: 56 additions & 0 deletions terraform/variables.tf
Original file line number Diff line number Diff line change
@@ -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_project_name>.pages.dev\"."
type = string
default = ""
}
Loading