Renovate configuration.

This commit is contained in:
2025-11-04 14:27:54 -05:00
27 changed files with 1495 additions and 483 deletions
@@ -0,0 +1,188 @@
name: Auto-PR Check/Creation and TF/OpenTofu Plan
on:
workflow_dispatch:
push:
branches-ignore:
- "main"
- "renovate/**"
paths:
- "cloudflare/**"
env:
OPENTOFU_VERSION: "1.10.6"
HC_VAULT_VERSION: "1.20.4"
TEA_VERSION: "0.10.1"
jobs:
check-and-create-pr:
name: Check and Create PR
outputs:
pr_number: ${{ steps.pr-check-create.outputs.pr_number }}
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Setting Vault Token
run: echo "VAULT_TOKEN=${{ secrets.VAULT_GITEA_TOKEN }}" >> $GITHUB_ENV
- name: Gotify Notification - Start
uses: eikendev/gotify-action@master
with:
gotify_api_base: ${{ secrets.RUNNER_GOTIFY_URL }}
gotify_app_token: ${{ secrets.RUNNER_GOTIFY_TOKEN }}
notification_title: "GITEA: PR Check @ Rinoa"
notification_message: "Checking for existing PR... 🔍"
- name: PR Check/Creation
id: pr-check-create
uses: https://git.trez.wtf/Trez/gitea-auto-pr@main
with:
url: ${{ secrets.TREZ_GITEA_URL }}
token: ${{ secrets.BOT_GITEA_TOKEN }}
pr-label: "docker-compose,manual"
assignee: ${{ github.actor }}
- name: Gotify Notification - Done
uses: eikendev/gotify-action@master
with:
gotify_api_base: ${{ secrets.RUNNER_GOTIFY_URL }}
gotify_app_token: ${{ secrets.RUNNER_GOTIFY_TOKEN }}
notification_title: "GITEA: PR Check @ Rinoa"
notification_message: "PR Check done 🎟️"
plan-approval:
name: OpenTofu Plan
needs: check-and-create-pr
runs-on: ubuntu-latest
env:
VAULT_TOKEN: ${{ secrets.VAULT_GITEA_TOKEN }}
outputs:
tofu-cloudflare-plan: ${{ steps.tofu_plan.outputs.plan-output }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup OpenTofu
uses: opentofu/setup-opentofu@v1.0.6
with:
version: ${{ env.OPENTOFU_VERSION }}
tofu_wrapper: true
- name: Generate .env from Hashicorp Vault
uses: https://git.trez.wtf/Trez/hc-vault-env@main
with:
HC_VAULT_VERSION: ${{ env.HC_VAULT_VERSION }}
HC_VAULT_ADDR: ${{ secrets.TREZ_VAULT_ADDR }}
HC_VAULT_AUTH: token
HC_VAULT_TOKEN: ${{ env.VAULT_TOKEN }}
HC_VAULT_SECRETS_PATH: tar-valon-terraform/env
ENV_FILE_NAME: cloudflare/.env
- name: Export env vars from Vault .env
id: env-vault-vars
run: |
sed -i 's/[\"'\'']//g' cloudflare/.env
set -a
source cloudflare/.env
set +a
while IFS='=' read -r key value; do
if [[ -n "$key" ]]; then
echo "$key=$value" >> $GITHUB_ENV
fi
done < cloudflare/.env
repo_name=$(echo "${{ github.repository }}" | awk -F"/" '{print $2}')
echo "repo_name=$repo_name" >> "$GITHUB_OUTPUT"
- name: Run tofu init
uses: dnogu/tofu-init@v1
with:
working-directory: .
chdir: cloudflare
- name: Tofu Plan
id: tofu_plan
continue-on-error: true
uses: dnogu/tofu-plan@v1
with:
working-directory: .
chdir: cloudflare
out: cloudflare.tfplan
- name: Build Markdown PR comment from plan file
run: |
mkdir -p tmp
{
echo "## 🧩 OpenTofu Plan — Cloudflare"
echo "**Branch:** \`${{ github.ref_name }}\`"
echo "**Exit Code:** \`${{ steps.tofu_plan.outputs.exitcode }}\`"
echo "**Working Directory:** \`cloudflare\`"
echo ""
echo "<details><summary>🪶 Click to expand full plan output</summary>"
echo ""
echo '```hcl'
cat ${GITHUB_WORKSPACE}/cloudflare/cloudflare.tfplan # <-- read file directly, ACT-safe
echo '```'
echo ""
echo "</details>"
echo ""
echo "*(This plan was automatically generated by the workflow.)*"
} > tmp/tofu-plan.md
echo "Markdown PR comment built: tmp/tofu-plan.md"
- name: Comment full Tofu Plan on PR (Gitea safe)
uses: https://git.trez.wtf/Trez.One/git-auto-comment@main
with:
platform: gitea
api_url: https://git.trez.wtf/api/v1
token: ${{ secrets.BOT_GITEA_TOKEN }}
pr_index: ${{ needs.check-and-create-pr.outputs.pr_number }}
repo_owner: ${{ github.repository_owner }}
repo_name: ${{ steps.env-vault-vars.outputs.repo_name }}
comment_template_path: tmp/tofu-plan.md
- name: Wait for manual approval
uses: trstringer/manual-approval@v1
with:
secret: ${{ secrets.BOT_GITEA_TOKEN }}
approvers: WTF
minimum-approvals: 1
issue-title: "Tofu Plan for ${{ needs.check-and-create-pr.outputs.pr_number }}"
issue-body: "Please approve or deny the deployment of the below Tofu plan"
issue-body-file-path: cloudflare/cloudflare.tfplan
exclude-workflow-initiator-as-approver: false
fail-on-denial: true
# apply:
# name: Apply Tofu Plan
# needs: plan-approval
# runs-on: ubuntu-latest
# if: ${{ needs.plan-approval.result == 'success' }}
# env:
# VAULT_TOKEN: ${{ secrets.VAULT_GITEA_TOKEN }}
# steps:
# - name: Checkout
# uses: actions/checkout@v4
#
# - name: Setup OpenTofu
# uses: opentofu/setup-opentofu@v1.0.6
# with:
# version: ${{ env.OPENTOFU_VERSION }}
# tofu_wrapper: true
#
# - name: Export env from Vault
# run: |
# set -a
# source cloudflare/.env
# set +a
#
# - name: Run Tofu Apply
# uses: dnogu/tofu-apply@v1
# with:
# working-directory: .
# chdir: cloudflare
# plan: cloudflare.tfplan
+65
View File
@@ -0,0 +1,65 @@
name: Renovate
on:
schedule:
- cron: "0/30 * * * *"
workflow_dispatch:
env:
RENOVATE_VERSION: "41.146.8"
jobs:
renovate:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Renovate Run
env:
DOCKER_HOST: tcp://dockerproxy:2375
RENOVATE_PLATFORM: gitea
RENOVATE_ENDPOINT: https://git.trez.wtf/api/v1
RENOVATE_TOKEN: ${{ secrets.RENOVATE_BOT_TOKEN }}
LOG_LEVEL: ${{ vars.RENOVATE_LOG_LEVEL }}
RENOVATE_GITHUB_COM_TOKEN: ${{ secrets.RENOVATE_GITHUB_TOKEN }}
RENOVATE_CONFIG_FILE: renovate.json
RENOVATE_REPOSITORIES: trez/rinoa-docker
RENOVATE_HOST_RULES: |
[
{
"description": "Docker Hub authentication",
"hostType": "docker",
"matchHost": "docker.io",
"username": "${{ secrets.DOCKERHUB_USER }}",
"password": "${{ secrets.DOCKERHUB_PASSWORD }}"
},
{
"description": "GitHub Container Registry (GHCR)",
"hostType": "docker",
"matchHost": "ghcr.io",
"username": "${{ secrets.GHCR_USER }}",
"password": "${{ secrets.GHCR_LOGIN_TOKEN }}"
},
{
"description": "Self-hosted Gitea Docker Registry",
"hostType": "docker",
"matchHost": "git.trez.wtf",
"username": "${{ secrets.BOT_GITEA_USER }}",
"password": "${{ secrets.BOT_GITEA_PASSWORD }}"
}
]
run: |
docker run --rm \
-e RENOVATE_PLATFORM \
-e RENOVATE_ENDPOINT \
-e RENOVATE_TOKEN \
-e LOG_LEVEL \
-e RENOVATE_GITHUB_COM_TOKEN \
-e RENOVATE_CONFIG_FILE \
-e RENOVATE_REPOSITORIES \
-e RENOVATE_HOST_RULES \
--volumes-from ${{ env.JOB_CONTAINER_NAME }} \
-w ${GITHUB_WORKSPACE} \
renovate/renovate:${{ env.RENOVATE_VERSION }}-full
+11 -7
View File
@@ -1,5 +1,9 @@
# Created by https://www.toptal.com/api/terraform
# Edit at https://www.toptal.com?templates=terraform
### Terraform ###
# Local .terraform directories
.terraform/
**/.terraform/*
# .tfstate files
*.tfstate
@@ -10,8 +14,8 @@ crash.log
crash.*.log
# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars
*.tfvars.json
@@ -23,20 +27,20 @@ override.tf.json
*_override.tf
*_override.tf.json
# Ignore transient lock info files created by terraform apply
.terraform.tfstate.lock.info
*.terraform.lock*
# Include override files you do wish to add to version control using negated pattern
# !example_override.tf
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*
**/*.tfplan
# Ignore CLI configuration files
.terraformrc
terraform.rc
# End of https://www.toptal.com/api/terraform
# Envs
*.env*
**/.cf-terraforming
**/*.txt
**/cloudflare_resource_imp.tf
-2
View File
@@ -1,2 +0,0 @@
email: "charish.patel@trez.wtf"
key: "3dfcfd53a7af10f027ade7467a087017867e7"
+10 -10
View File
@@ -2,18 +2,18 @@
# Manual edits may be lost in future updates.
provider "registry.opentofu.org/cloudflare/cloudflare" {
version = "5.7.1"
version = "5.12.0"
constraints = "~> 5.0"
hashes = [
"h1:24uCJp1H3ZK8CCwszYyR3qtOn5Z5Hn1eCcv2gkH7+4g=",
"zh:0bbd2eaa8210b2214dc426a2471b38cee53db9750b4916b34b0926fc9cbe4d7b",
"zh:0de3675e2ace7478ab0d354b2b6db4be2ae5a9a5e68b725cdd10e956131ec687",
"zh:4787a255919911aac5e1f8d47ed19fa45e5b90439ecf1fffbb17ff8bfb28de79",
"zh:4eb1e4300b3cdee3a323457ebcc8df29a735ea6bbabe3cf9cbd3dc3fb5a9172b",
"zh:580bbd5a727c9e3f31ae47c872df860c9a08ea998e0ff3dfe37dbac536146166",
"zh:6a359212678ffcf88551e2d8d0f8e52418031cf1f8077bb8ddf500171ee90f2b",
"zh:bec6890cb11511577c5f8ec8954e26ac51c44a114cb3e0349fea40f87930c029",
"zh:dbe3585510283c8e53a2b24cc7a69fab0ee9d71addae0db1be0374bc32fc6355",
"h1:IvMPMJrmyw6x+8GZklY7qb8VXrjr00zwsN+TFlxkCTM=",
"zh:06166a72e69eb712ad2c8b49c1ed060223b0d57bb95ce5f6c8440ce19253913e",
"zh:484c32dc4fbe1f7baaf00f8d0d1774d259e1a602aebf60b8dea8c6dd122c1d27",
"zh:914b4796a5f2c5914cb94864a7541ce132c0e287bf49a5328706d50152117bc4",
"zh:bbcf3effe11ad44988c2aa4482c3fd0089ca86527463a9a873cecda1a4a022bc",
"zh:c2a59f29b4b4c0344dbb9ab3d78ebcc1d32153f1fd7e919eba7edf7d825119c2",
"zh:d6900b39b9c58743e6b1f05b2db7c39276c94f74d501f23bebb88d413266c57c",
"zh:f000d33075c30e616df8e58e341614e958eed4a51f3427d2e1a18ea1b7e0c6c6",
"zh:f809ab383cca0a5f83072981c64208cbd7fa67e986a86ee02dd2c82333221e32",
"zh:ff4fd5b3b0327f8f41fc65d909839288fb98ecfe32a9aff11d2e2638f2109302",
]
}
-4
View File
@@ -1,4 +0,0 @@
email: ""
key: ""
# or
token: ""
@@ -1,5 +0,0 @@
resource "cloudflare_argo_tiered_caching" "terraform_managed_resource_tiered_caching_0" {
value = "on"
zone_id = "17dbb71212204583b777783d25eb6738"
}
@@ -1,25 +0,0 @@
resource "cloudflare_email_routing_dns" "terraform_managed_resource_17dbb71212204583b777783d25eb6738_0" {
name = "trez.wtf"
zone_id = "17dbb71212204583b777783d25eb6738"
}
resource "cloudflare_email_routing_dns" "terraform_managed_resource_17dbb71212204583b777783d25eb6738_1" {
name = "trez.wtf"
zone_id = "17dbb71212204583b777783d25eb6738"
}
resource "cloudflare_email_routing_dns" "terraform_managed_resource_17dbb71212204583b777783d25eb6738_2" {
name = "trez.wtf"
zone_id = "17dbb71212204583b777783d25eb6738"
}
resource "cloudflare_email_routing_dns" "terraform_managed_resource_17dbb71212204583b777783d25eb6738_3" {
name = "cf2024-1._domainkey.trez.wtf"
zone_id = "17dbb71212204583b777783d25eb6738"
}
resource "cloudflare_email_routing_dns" "terraform_managed_resource_17dbb71212204583b777783d25eb6738_4" {
name = "trez.wtf"
zone_id = "17dbb71212204583b777783d25eb6738"
}
@@ -1,4 +0,0 @@
resource "cloudflare_email_routing_settings" "terraform_managed_resource_17dbb71212204583b777783d25eb6738_0" {
zone_id = "17dbb71212204583b777783d25eb6738"
}
File diff suppressed because it is too large Load Diff
-5
View File
@@ -1,5 +0,0 @@
resource "cloudflare_tiered_cache" "terraform_managed_resource_tiered_cache_smart_topology_enable_0" {
value = "on"
zone_id = "17dbb71212204583b777783d25eb6738"
}
-10
View File
@@ -1,10 +0,0 @@
resource "cloudflare_zone" "terraform_managed_resource_17dbb71212204583b777783d25eb6738_0" {
name = "trez.wtf"
paused = false
type = "full"
account = {
id = "f5a5c0098ccae27fb0486ffbc2ee6087"
name = "Charish.patel@trez.wtf's Account"
}
}
-6
View File
@@ -1,6 +0,0 @@
resource "cloudflare_zone_dnssec" "terraform_managed_resource_17dbb71212204583b777783d25eb6738_0" {
dnssec_multi_signer = true
status = "active"
zone_id = "17dbb71212204583b777783d25eb6738"
}
+6
View File
@@ -0,0 +1,6 @@
CLOUDFLARE_API_TOKEN=""
CLOUDFLARE_ZONE_ID=""
AWS_ENDPOINT_URL_S3=""
AWS_DEFAULT_REGION=""
AWS_ACCESS_KEY_ID=""
AWS_SECRET_ACCESS_KEY=""
+218 -169
View File
@@ -1,181 +1,230 @@
#!/bin/bash
#!/usr/bin/env bash
set -euo pipefail
cf_generate () {
resources=(
cloudflare_account
cloudflare_account_member
cloudflare_account_subscription
cloudflare_address_map
cloudflare_api_shield_discovery_operation
cloudflare_api_shield_operation
cloudflare_api_shield_operation_schema_validation_settings
cloudflare_api_shield_schema
cloudflare_api_shield_schema_validation_settings
cloudflare_argo_smart_routing
cloudflare_argo_tiered_caching
cloudflare_authenticated_origin_pulls
cloudflare_authenticated_origin_pulls_certificate
cloudflare_bot_management
cloudflare_calls_sfu_app
cloudflare_calls_turn_app
cloudflare_certificate_pack
cloudflare_content_scanning_expression
cloudflare_custom_hostname
cloudflare_custom_hostname_fallback_origin
cloudflare_d1_database
cloudflare_dns_firewall
cloudflare_dns_record
cloudflare_dns_zone_transfers_acl
cloudflare_dns_zone_transfers_incoming
cloudflare_dns_zone_transfers_outgoing
cloudflare_dns_zone_transfers_peer
cloudflare_dns_zone_transfers_tsig
cloudflare_email_routing_address
cloudflare_email_routing_catch_all
cloudflare_email_routing_dns
cloudflare_email_routing_rule
cloudflare_email_routing_settings
cloudflare_email_security_block_sender
cloudflare_email_security_impersonation_registry
cloudflare_email_security_trusted_domains
cloudflare_filter
cloudflare_healthcheck
cloudflare_hostname_tls_setting
cloudflare_keyless_certificate
cloudflare_leaked_credential_check
cloudflare_leaked_credential_check_rule
cloudflare_list
cloudflare_list_item
cloudflare_load_balancer
cloudflare_load_balancer_monitor
cloudflare_load_balancer_pool
cloudflare_logpull_retention
cloudflare_logpush_job
cloudflare_magic_wan_static_route
cloudflare_managed_transforms
cloudflare_mtls_certificate
cloudflare_notification_policy
cloudflare_notification_policy_webhooks
cloudflare_observatory_scheduled_test
cloudflare_origin_ca_certificate
cloudflare_page_rule
cloudflare_page_shield_policy
cloudflare_pages_domain
cloudflare_pages_project
cloudflare_queue
cloudflare_queue_consumer
cloudflare_r2_bucket
cloudflare_r2_custom_domain
cloudflare_r2_managed_domain
cloudflare_rate_limit
cloudflare_regional_hostname
cloudflare_regional_tiered_cache
cloudflare_registrar_domain
cloudflare_ruleset
cloudflare_snippet_rules
cloudflare_snippets
cloudflare_spectrum_application
cloudflare_stream
cloudflare_stream_key
cloudflare_stream_live_input
cloudflare_stream_watermark
cloudflare_stream_webhook
cloudflare_tiered_cache
cloudflare_total_tls
cloudflare_turnstile_widget
cloudflare_url_normalization_settings
cloudflare_user
cloudflare_waiting_room
cloudflare_waiting_room_event
cloudflare_waiting_room_rules
cloudflare_waiting_room_settings
cloudflare_web3_hostname
cloudflare_web_analytics_rule
cloudflare_web_analytics_site
cloudflare_workers_cron_trigger
cloudflare_workers_custom_domain
cloudflare_workers_deployment
cloudflare_workers_for_platforms_dispatch_namespace
cloudflare_workers_kv_namespace
cloudflare_workers_script_subdomain
cloudflare_zero_trust_access_application
cloudflare_zero_trust_access_custom_page
cloudflare_zero_trust_access_group
cloudflare_zero_trust_access_identity_provider
cloudflare_zero_trust_access_infrastructure_target
cloudflare_zero_trust_access_key_configuration
cloudflare_zero_trust_access_mtls_certificate
cloudflare_zero_trust_access_mtls_hostname_settings
cloudflare_zero_trust_access_policy
cloudflare_zero_trust_access_service_token
cloudflare_zero_trust_access_short_lived_certificate
cloudflare_zero_trust_access_tag
cloudflare_zero_trust_device_custom_profile
cloudflare_zero_trust_device_default_profile
cloudflare_zero_trust_device_default_profile_certificates
cloudflare_zero_trust_device_default_profile_local_domain_fallback
cloudflare_zero_trust_device_managed_networks
cloudflare_zero_trust_device_posture_integration
cloudflare_zero_trust_device_posture_rule
cloudflare_zero_trust_dex_test
cloudflare_zero_trust_dlp_custom_profile
cloudflare_zero_trust_dlp_dataset
cloudflare_zero_trust_dlp_predefined_profile
cloudflare_zero_trust_dns_location
cloudflare_zero_trust_gateway_certificate
cloudflare_zero_trust_gateway_policy
cloudflare_zero_trust_gateway_proxy_endpoint
cloudflare_zero_trust_gateway_settings
cloudflare_zero_trust_list
cloudflare_zero_trust_organization
cloudflare_zero_trust_risk_behavior
cloudflare_zero_trust_risk_scoring_integration
cloudflare_zero_trust_tunnel_cloudflared
cloudflare_zero_trust_tunnel_cloudflared_config
cloudflare_zero_trust_tunnel_cloudflared_route
cloudflare_zero_trust_tunnel_cloudflared_virtual_network
cloudflare_zone
cloudflare_zone_cache_reserve
cloudflare_zone_cache_variants
cloudflare_zone_dnssec
cloudflare_zone_lockdown
cloudflare_zone_setting
)
# -------------------------------
# Detect Terraform binary: tofu vs terraform
# -------------------------------
if command -v tofu &>/dev/null; then
TF_BIN="tofu"
elif command -v terraform &>/dev/null; then
TF_BIN="terraform"
else
echo "❌ Neither 'terraform' nor 'tofu' found in PATH"
exit 1
fi
for resource in "${resources[@]}"; do
echo "Generating ${resource}.tf..."
cf-terraforming generate --config .cf-terraforming.yaml --resource-type ${resource} > ${resource}.tf
done
echo "️ Using $TF_BIN for Terraform operations"
echo "🧹 Cleaning up empty files..."
find . -size 0 -name "*.tf" | xargs rm
echo "✅ All Terraform files generated!"
# -------------------------------
# Ensure CF API token
# -------------------------------
CF_API_TOKEN="${CLOUDFLARE_API_TOKEN:-}"
if [[ -z "${CF_API_TOKEN}" ]]; then
echo "Please set CF_API_TOKEN before running this script."
exit 1
fi
# -------------------------------
# Helper: fetch paginated results from Cloudflare API
# -------------------------------
cf_paginate() {
local endpoint="$1"
local page=1
local per_page=100
while :; do
local result
result=$(curl -s -X GET "${endpoint}?page=${page}&per_page=${per_page}" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json")
local items
items=$(echo "${result}" | jq -r '.result[]? | @base64')
[[ -z "$items" ]] && break
echo "$items"
local total_pages
total_pages=$(echo "$result" | jq -r '.result_info.total_pages')
((page++))
[[ $page -gt $total_pages ]] && break
done
}
cf_import () {
for cfresource in $(find . -type f -name "cloudflare_*.tf"); do
echo "Importing ${cfresource}..."
cf-terraforming import --modern-import-block --config .cf-terraforming.yaml --resource-type ${cfresource}
done
# -------------------------------
# Generate Cloudflare resources using cf-terraforming
# -------------------------------
generate_resources() {
echo "🔧 Generating Cloudflare resources via cf-terraforming..."
local output_file="cloudflare_resource_gen.tf"
> "${output_file}"
echo "✅ All Cloudflare resources import."
resources=(
cloudflare_account
cloudflare_account_member
cloudflare_account_subscription
cloudflare_address_map
cloudflare_argo_tiered_caching
cloudflare_authenticated_origin_pulls
cloudflare_authenticated_origin_pulls_certificate
cloudflare_bot_management
cloudflare_certificate_pack
cloudflare_content_scanning_expression
cloudflare_custom_hostname
cloudflare_custom_hostname_fallback_origin
cloudflare_d1_database
cloudflare_dns_firewall
cloudflare_dns_record
cloudflare_dns_zone_transfers_acl
cloudflare_dns_zone_transfers_incoming
cloudflare_dns_zone_transfers_outgoing
cloudflare_dns_zone_transfers_peer
cloudflare_dns_zone_transfers_tsig
cloudflare_email_routing_address
cloudflare_email_routing_catch_all
cloudflare_email_routing_dns
cloudflare_email_routing_rule
cloudflare_email_routing_settings
cloudflare_filter
cloudflare_healthcheck
cloudflare_hostname_tls_setting
cloudflare_keyless_certificate
cloudflare_leaked_credential_check
cloudflare_leaked_credential_check_rule
cloudflare_list_item
cloudflare_load_balancer
cloudflare_load_balancer_monitor
cloudflare_load_balancer_pool
cloudflare_logpull_retention
cloudflare_logpush_job
cloudflare_magic_wan_static_route
cloudflare_managed_transforms
cloudflare_mtls_certificate
cloudflare_notification_policy
cloudflare_notification_policy_webhooks
cloudflare_observatory_scheduled_test
cloudflare_origin_ca_certificate
cloudflare_page_rule
cloudflare_page_shield_policy
cloudflare_pages_domain
cloudflare_pages_project
cloudflare_queue
cloudflare_queue_consumer
cloudflare_r2_bucket
cloudflare_r2_custom_domain
cloudflare_r2_managed_domain
cloudflare_rate_limit
cloudflare_regional_hostname
cloudflare_regional_tiered_cache
cloudflare_registrar_domain
cloudflare_ruleset
cloudflare_snippet_rules
cloudflare_snippets
cloudflare_spectrum_application
cloudflare_stream
cloudflare_stream_key
cloudflare_stream_live_input
cloudflare_stream_watermark
cloudflare_stream_webhook
cloudflare_tiered_cache
cloudflare_total_tls
cloudflare_turnstile_widget
cloudflare_url_normalization_settings
cloudflare_user
cloudflare_waiting_room
cloudflare_waiting_room_event
cloudflare_waiting_room_rules
cloudflare_waiting_room_settings
cloudflare_web3_hostname
cloudflare_web_analytics_rule
cloudflare_web_analytics_site
cloudflare_workers_cron_trigger
cloudflare_workers_custom_domain
cloudflare_workers_deployment
cloudflare_workers_for_platforms_dispatch_namespace
cloudflare_workers_kv_namespace
cloudflare_workers_script_subdomain
cloudflare_zone
cloudflare_zone_cache_reserve
cloudflare_zone_cache_variants
cloudflare_zone_dnssec
cloudflare_zone_lockdown
cloudflare_zone_setting
)
for r in "${resources[@]}"; do
echo "Generating $r ..."
cf-terraforming generate \
--token "${CF_API_TOKEN}" \
--resource-type "${r}" >> "${output_file}" || true
done
echo "✅ Terraform resources generated in ${output_file}"
}
# Prompt user for input
# -------------------------------
# Import Cloudflare resources into state using cf-terraforming
# -------------------------------
import_zone_resources() {
local zone_id="$1"
local zone_name="$2"
echo "⏳ Importing zone $zone_name ..."
cf-terraforming import \
--token "${CF_API_TOKEN}" \
--modern-import-block \
--resource-type cloudflare_zone \
--resource-id "$zone_id" >> cloudflare_resource_imp.tf || true
echo "✅ Imported cloudflare_zone for $zone_name"
echo "🔄 Importing DNS records for $zone_name ..."
cf-terraforming import \
--token "${CF_API_TOKEN}" \
--zone "$zone_id" \
--modern-import-block \
--resource-type cloudflare_dns_record >> cloudflare_resource_imp.tf || true
echo "✅ Imported DNS records for $zone_name"
# Optional: import other zone-level resources
for res in cloudflare_argo_tiered_caching cloudflare_email_routing_settings cloudflare_tiered_cache cloudflare_zone_dnssec; do
cf-terraforming import \
--token "${CF_API_TOKEN}" \
--resource-type "$res" \
--modern-import-block \
--resource-id "$zone_id" >> cloudflare_resource_imp.tf || true
echo "✅ Imported $res for $zone_name"
done
}
# -------------------------------
# Main
# -------------------------------
echo "Choose an option:"
echo "1) Generate Cloudflare Terraform resources"
echo "2) Import Cloudflare Terraform resources"
read -rp "Enter 1 or 2: " user_choice
echo "2) Import Cloudflare Terraform resources into state"
read -rp "Enter 1 or 2: " choice
case "$user_choice" in
1)
cf_generate
;;
2)
cf_import
;;
*)
echo "Invalid option. Please enter 1 or 2."
exit 1
;;
case "$choice" in
1)
generate_resources
;;
2)
echo "🔄 Fetching zones..."
zones=$(cf_paginate "https://api.cloudflare.com/client/v4/zones")
declare -A zone_map
while read -r z; do
zname=$(echo "$z" | base64 --decode | jq -r '.name')
zid=$(echo "$z" | base64 --decode | jq -r '.id')
zone_map["$zname"]="$zid"
done <<< "$zones"
echo "⚡ Found ${#zone_map[@]} zones."
for zone_name in "${!zone_map[@]}"; do
zid="${zone_map[$zone_name]}"
import_zone_resources "$zid" "$zone_name"
done
;;
*)
echo "Invalid option. Enter 1 or 2."
exit 1
;;
esac
echo "🎉 All operations completed!"
+2 -10
View File
@@ -7,12 +7,8 @@ terraform {
}
backend "s3" {
bucket = "rinoa-terraform"
key = "cloudflare/.tfstate"
endpoints = { s3 = "http://192.168.1.254:9001" }
region = "us-east-fh-pln"
bucket = "rinoa-terraform"
key = "cloudflare/.tfstate"
skip_credentials_validation = true
skip_metadata_api_check = true
skip_region_validation = true
@@ -20,7 +16,3 @@ terraform {
skip_requesting_account_id = true # Optional, set to true if MinIO does not support AWS account ID
}
}
provider "cloudflare" {
api_token = var.cloudflare_token
}
Binary file not shown.
-24
View File
@@ -1,24 +0,0 @@
# variable "cloudflare_zone_id" {
# description = "The Cloudflare UUID for the Zone to use."
# type = string
# }
variable "cloudflare_account_id" {
description = "The Cloudflare UUID for the Account the Zone lives in."
type = string
}
variable "cloudflare_email" {
description = "The Cloudflare user."
type = string
}
variable "cloudflare_token" {
description = "The Cloudflare user's API token."
type = string
}
# variable "zone" {
# description = "The Cloudflare user's API token."
# type = string
# }
+1
View File
@@ -0,0 +1 @@
GITEA_TOKEN=""
+6
View File
@@ -0,0 +1,6 @@
// Generated by gitea-terraforming (OpenTofu compatible)
# source id=52
resource "gitea_organization" "org_trez" {
name = "Trez"
}
+121
View File
@@ -0,0 +1,121 @@
// Generated by gitea-terraforming (OpenTofu compatible)
# source id=5
resource "gitea_repository" "repo_trez_rinoa-docker" {
owner = "Trez"
name = "rinoa-docker"
private = false
description = ""
}
# source id=9
resource "gitea_repository" "repo_trez_meraki-naemon" {
owner = "Trez"
name = "meraki-naemon"
private = false
description = ""
}
# source id=13
resource "gitea_repository" "repo_trez_benedikta-ovos" {
owner = "Trez"
name = "benedikta-ovos"
private = false
description = ""
}
# source id=16
resource "gitea_repository" "repo_trez_rikku-home-assistant" {
owner = "Trez"
name = "rikku-home-assistant"
private = false
description = ""
}
# source id=17
resource "gitea_repository" "repo_trez_tar-valon-terraform" {
owner = "Trez"
name = "tar-valon-terraform"
private = true
description = ""
}
# source id=18
resource "gitea_repository" "repo_trez_hugo_it-services" {
owner = "Trez"
name = "hugo_it-services"
private = false
description = ""
}
# source id=19
resource "gitea_repository" "repo_trez_docker-mods-uptime-kuma-timeout-fix" {
owner = "Trez"
name = "docker-mods-uptime-kuma-timeout-fix"
private = false
description = "Documentation and Examples of base container modifications"
}
# source id=21
resource "gitea_repository" "repo_trez_tar-valon-ansible" {
owner = "Trez"
name = "tar-valon-ansible"
private = false
description = ""
}
# source id=22
resource "gitea_repository" "repo_trez_congo-hindi-gujarati" {
owner = "Trez"
name = "congo-hindi-gujarati"
private = false
description = "A powerful, lightweight theme for Hugo built with Tailwind CSS."
}
# source id=26
resource "gitea_repository" "repo_trez_action-home-assistant" {
owner = "Trez"
name = "action-home-assistant"
private = false
description = "🚀 Frenck's GitHub Action for running a Home Assistant Core configuration check"
}
# source id=27
resource "gitea_repository" "repo_trez_renovate-config" {
owner = "Trez"
name = "renovate-config"
private = false
description = ""
}
# source id=31
resource "gitea_repository" "repo_trez_hc-vault-env" {
owner = "Trez"
name = "hc-vault-env"
private = false
description = ""
}
# source id=32
resource "gitea_repository" "repo_trez_docker-select-image-pull" {
owner = "Trez"
name = "docker-select-image-pull"
private = false
description = ""
}
# source id=33
resource "gitea_repository" "repo_trez_gitea-auto-pr" {
owner = "Trez"
name = "gitea-auto-pr"
private = false
description = ""
}
# source id=34
resource "gitea_repository" "repo_trez_ultima-ai" {
owner = "Trez"
name = "ultima-ai"
private = true
description = ""
}
+274
View File
@@ -0,0 +1,274 @@
#!/usr/bin/env python3
"""
gitea-terraforming: Reverse Terraform for Gitea (OpenTofu compatible)
Generates Terraform HCL for:
- Users
- Organizations
- Repositories (user & org)
- Branch protections
Output files are automatically split per resource type:
gitea-<resource-type>.tf
Supports import generation:
- Modern import blocks (--modern-import-block)
- Shell script terraform import
Usage example:
python gitea_terraforming.py --api https://gitea.example.com --token <ADMIN_TOKEN> --out-dir ./gitea_tf
"""
import argparse, os, sys, time, json, re
from typing import Any, Dict, List, Optional
from datetime import datetime
import requests
def slugify(s: str) -> str:
s = re.sub(r'[^0-9a-zA-Z_-]', '_', s)
return re.sub('_+', '_', s).strip('_').lower()
class GiteaClient:
def __init__(self, api_base: str, token: str, verify: bool = True):
self.base = api_base.rstrip('/')
self.s = requests.Session()
self.s.headers.update({
'Authorization': f'token {token}',
'Accept': 'application/json',
'User-Agent': 'gitea-terraforming/0.1'
})
self.verify = verify
def _get(self, path: str, params: Optional[dict] = None):
url = f"{self.base}{path}"
out = []
page = 1
while True:
qp = params.copy() if params else {}
qp.update({'page': page, 'limit': 100})
resp = self.s.get(url, params=qp, verify=self.verify, timeout=30)
if resp.status_code == 404:
return []
if resp.status_code == 429:
retry = int(resp.headers.get('Retry-After', '5'))
time.sleep(retry)
continue
resp.raise_for_status()
data = resp.json()
if isinstance(data, list):
out.extend(data)
if len(data) < 100:
break
page += 1
else:
return data
return out
def list_orgs(self) -> List[dict]:
return self._get("/api/v1/orgs")
def list_users(self) -> List[dict]:
return self._get("/api/v1/admin/users")
def list_user_repos(self, user: str) -> List[dict]:
return self._get(f"/api/v1/users/{user}/repos")
def list_org_repos(self, org: str) -> List[dict]:
return self._get(f"/api/v1/orgs/{org}/repos")
def list_branch_protections(self, owner: str, repo: str) -> List[dict]:
return self._get(f"/api/v1/repos/{owner}/{repo}/branch_protections")
def hcl_block(resource_type: str, name: str, attrs: dict, comment: Optional[str] = None) -> str:
lines = []
if comment:
lines.append(f"# {comment}")
lines.append(f'resource "{resource_type}" "{name}" ' + "{")
for k, v in attrs.items():
if v is None:
continue
if isinstance(v, bool):
lines.append(f" {k} = {str(v).lower()}")
elif isinstance(v, (int, float)):
lines.append(f" {k} = {v}")
elif isinstance(v, str):
safe = v.replace('"', '\\"')
lines.append(f' {k} = "{safe}"')
elif isinstance(v, list):
joined = ", ".join([f'"{x}"' for x in v])
lines.append(f" {k} = [{joined}]")
else:
lines.append(f' # {k} = {json.dumps(v)}')
lines.append("}\n")
return "\n".join(lines)
def modern_import_block(to: str, ident: str) -> str:
return f'import {{\n to = {to}\n id = "{ident}"\n}}\n'
def generate(api: str, token: str, out_dir: str, modern: bool = False, dry: bool = False):
client = GiteaClient(api, token)
os.makedirs(out_dir, exist_ok=True)
imports = []
files: dict = {}
orgs = client.list_orgs()
org_buf = []
for o in orgs:
uname = o.get("username") or o.get("user_name") or o.get("name")
rname = f"org_{slugify(uname)}"
attrs = {"name": uname}
org_buf.append(hcl_block("gitea_organization", rname, attrs, comment=f"source id={o.get('id')}"))
imports.append((f"gitea_organization.{rname}", uname))
if org_buf:
files["orgs"] = "\n".join(org_buf)
users = []
try:
users = client.list_users()
except Exception as e:
print(f"Warning: cannot list users: {e}", file=sys.stderr)
user_buf = []
for u in users:
uname = u.get("login") or u.get("username")
if not uname:
continue
rname = f"user_{slugify(uname)}"
attrs = {
"username": uname,
"email": u.get("email"),
"full_name": u.get("full_name"),
"is_admin": u.get("is_admin")
}
user_buf.append(hcl_block("gitea_user", rname, attrs, comment=f"source id={u.get('id')}"))
imports.append((f"gitea_user.{rname}", uname))
if user_buf:
files["users"] = "\n".join(user_buf)
repo_buf = []
for u in users:
uname = u.get("login") or u.get("username")
if not uname:
continue
try:
repos = client.list_user_repos(uname)
except Exception:
repos = []
for r in repos:
rname = f"repo_{slugify(uname)}_{slugify(r['name'])}"
attrs = {
"owner": uname,
"name": r["name"],
"private": r.get("private", False),
"description": r.get("description")
}
repo_buf.append(hcl_block("gitea_repository", rname, attrs, comment=f"source id={r.get('id')}"))
imports.append((f"gitea_repository.{rname}", f"{uname}/{r['name']}"))
for o in orgs:
uname = o.get("username") or o.get("user_name") or o.get("name")
if not uname:
continue
try:
repos = client.list_org_repos(uname)
except Exception:
repos = []
for r in repos:
rname = f"repo_{slugify(uname)}_{slugify(r['name'])}"
attrs = {
"owner": uname,
"name": r["name"],
"private": r.get("private", False),
"description": r.get("description")
}
repo_buf.append(hcl_block("gitea_repository", rname, attrs, comment=f"source id={r.get('id')}"))
imports.append((f"gitea_repository.{rname}", f"{uname}/{r['name']}"))
if repo_buf:
files["repos"] = "\n".join(repo_buf)
bp_buf = []
for to, ident in imports:
if not to.startswith("gitea_repository."):
continue
owner, repo = ident.split("/", 1)
try:
bps = client.list_branch_protections(owner, repo)
except Exception:
bps = []
for bp in bps:
branch_name = bp.get("branch_name") or bp.get("branch") or bp.get("name")
if branch_name is None:
continue
bn = f"branch_protection_{slugify(owner)}_{slugify(repo)}_{slugify(branch_name)}"
attrs = {
"repository": repo,
"owner": owner,
"branch": branch_name,
"enable_status_check": bp.get("enable_status_check", False),
"required_approvals": bp.get("required_approvals", 0),
"enable_merge_whitelist": bp.get("enable_merge_whitelist", False),
}
bp_buf.append(hcl_block("gitea_branch_protection", bn, attrs, comment=f"protect branch {branch_name}"))
imports.append((f"gitea_branch_protection.{bn}", f"{owner}/{repo}/{branch_name}"))
if bp_buf:
files["branches"] = "\n".join(bp_buf)
for rtype, content in files.items():
fname = f"gitea-{rtype}.tf"
fpath = os.path.join(out_dir, fname)
if dry:
print(f"--- {fpath} ---")
print(content)
else:
with open(fpath, "w", encoding="utf-8") as f:
f.write(f"// Generated by gitea-terraforming (OpenTofu compatible)\n\n")
f.write(content)
print(f"Wrote {fname}", file=sys.stderr)
if modern:
imps = "\n".join([modern_import_block(to, ident) for to, ident in imports])
imppath = os.path.join(out_dir, "imports.tf")
if dry:
print("--- imports.tf ---")
print(imps)
else:
with open(imppath, "w", encoding="utf-8") as f:
f.write("// Import blocks\n\n")
f.write(imps)
print(f"Wrote imports.tf", file=sys.stderr)
else:
lines = ["#!/usr/bin/env bash", "set -euo pipefail"]
lines += [f'terraform import {to} "{ident}"' for to, ident in imports]
imppath = os.path.join(out_dir, "terraform_imports.sh")
if dry:
print("--- terraform_imports.sh ---")
print("\n".join(lines))
else:
with open(imppath, "w", encoding="utf-8") as f:
f.write("\n".join(lines) + "\n")
os.chmod(imppath, 0o755)
print(f"Wrote terraform_imports.sh", file=sys.stderr)
def main():
parser = argparse.ArgumentParser(
description="gitea-terraforming: Reverse Terraform for Gitea (OpenTofu compatible)\n\n"
"Generates Terraform HCL for users, organizations, repositories, and branch protections.\n"
"Output files are split per resource type and timestamped.\n\n"
"Example usage:\n"
" python gitea_terraforming.py --api https://gitea.example.com --token <ADMIN_TOKEN> --out-dir ./gitea_tf\n",
formatter_class=argparse.RawTextHelpFormatter
)
parser.add_argument("--api", required=True, help="Gitea API URL (e.g., https://gitea.example.com)")
parser.add_argument("--token", default=os.environ.get("GITEA_TOKEN"),
help="Gitea admin token (or set GITEA_TOKEN environment variable)")
parser.add_argument("--out-dir", default="./gitea_tf", help="Directory to write Terraform files")
parser.add_argument("--modern-import-block", action="store_true",
help="Generate modern OpenTofu import blocks (imports.tf) instead of shell script")
parser.add_argument("--dry-run", action="store_true", help="Print output instead of writing files")
args = parser.parse_args()
if not args.token:
parser.error("Missing Gitea token: provide --token or set GITEA_TOKEN environment variable")
generate(args.api, args.token, args.out_dir, modern=args.modern_import_block, dry=args.dry_run)
if __name__ == "__main__":
main()
+33
View File
@@ -0,0 +1,33 @@
// Generated by gitea-terraforming (OpenTofu compatible)
# source id=3
resource "gitea_user" "user_gitea-sonarqube-bot" {
username = "gitea-sonarqube-bot"
email = "trezone@vivaldi.net"
full_name = ""
is_admin = false
}
# source id=51
resource "gitea_user" "user_renovate-bot" {
username = "renovate-bot"
email = "charish2k1@gmail.com"
full_name = ""
is_admin = false
}
# source id=1
resource "gitea_user" "user_root" {
username = "root"
email = "noreply@trez.wtf"
full_name = ""
is_admin = true
}
# source id=2
resource "gitea_user" "user_trez_one" {
username = "Trez.One"
email = "charish.patel@trez.wtf"
full_name = ""
is_admin = false
}
+101
View File
@@ -0,0 +1,101 @@
// Import blocks
import {
to = gitea_organization.org_trez
id = "Trez"
}
import {
to = gitea_user.user_gitea-sonarqube-bot
id = "gitea-sonarqube-bot"
}
import {
to = gitea_user.user_renovate-bot
id = "renovate-bot"
}
import {
to = gitea_user.user_root
id = "root"
}
import {
to = gitea_user.user_trez_one
id = "Trez.One"
}
import {
to = gitea_repository.repo_trez_rinoa-docker
id = "Trez/rinoa-docker"
}
import {
to = gitea_repository.repo_trez_meraki-naemon
id = "Trez/meraki-naemon"
}
import {
to = gitea_repository.repo_trez_benedikta-ovos
id = "Trez/benedikta-ovos"
}
import {
to = gitea_repository.repo_trez_rikku-home-assistant
id = "Trez/rikku-home-assistant"
}
import {
to = gitea_repository.repo_trez_tar-valon-terraform
id = "Trez/tar-valon-terraform"
}
import {
to = gitea_repository.repo_trez_hugo_it-services
id = "Trez/hugo_it-services"
}
import {
to = gitea_repository.repo_trez_docker-mods-uptime-kuma-timeout-fix
id = "Trez/docker-mods-uptime-kuma-timeout-fix"
}
import {
to = gitea_repository.repo_trez_tar-valon-ansible
id = "Trez/tar-valon-ansible"
}
import {
to = gitea_repository.repo_trez_congo-hindi-gujarati
id = "Trez/congo-hindi-gujarati"
}
import {
to = gitea_repository.repo_trez_action-home-assistant
id = "Trez/action-home-assistant"
}
import {
to = gitea_repository.repo_trez_renovate-config
id = "Trez/renovate-config"
}
import {
to = gitea_repository.repo_trez_hc-vault-env
id = "Trez/hc-vault-env"
}
import {
to = gitea_repository.repo_trez_docker-select-image-pull
id = "Trez/docker-select-image-pull"
}
import {
to = gitea_repository.repo_trez_gitea-auto-pr
id = "Trez/gitea-auto-pr"
}
import {
to = gitea_repository.repo_trez_ultima-ai
id = "Trez/ultima-ai"
}
+4
View File
@@ -2,6 +2,10 @@
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"local>Trez/renovate-config"
<<<<<<< HEAD
],
"enabledManagers": ["terraform"]
=======
]
>>>>>>> refs/remotes/origin/renovate/configure
}
+2
View File
@@ -0,0 +1,2 @@
SIGNOZ_ACCESS_TOKEN=""
SIGNOZ_ENDPOINT=""
+1 -1
View File
@@ -2,7 +2,7 @@ terraform {
required_providers {
signoz = {
source = "signoz/signoz"
version = "0.0.8"
version = "0.0.9"
}
}