This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Validate and Update DNS | |
| on: | |
| pull_request: | |
| paths: [domains/**] | |
| types: [opened, synchronize, reopened] | |
| push: | |
| paths: [domains/**] | |
| branches: [main] | |
| jobs: | |
| validate: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| is_valid: ${{ steps.validation.outputs.is_valid }} | |
| changed_files: ${{ steps.get-changes.outputs.changed_files }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Get changed files | |
| id: get-changes | |
| run: | | |
| echo "π Detecting changed files..." | |
| if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| echo "Mode: Pull Request" | |
| git fetch origin ${{ github.base_ref }} | |
| CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }} ${{ github.sha }} -- domains/ | tr '\n' ' ' || true) | |
| else | |
| echo "Mode: Push to main" | |
| if git log --oneline -2 > /dev/null 2>&1; then | |
| CHANGED_FILES=$(git diff --name-only HEAD^ HEAD -- domains/ | tr '\n' ' ' || true) | |
| else | |
| CHANGED_FILES=$(find domains/ -name "*.json" | tr '\n' ' ') | |
| fi | |
| fi | |
| echo "Changed files found: $CHANGED_FILES" | |
| echo "changed_files=$CHANGED_FILES" >> $GITHUB_OUTPUT | |
| - name: Validate JSON files | |
| id: validation | |
| run: | | |
| echo "Starting validation..." | |
| FILES="${{ steps.get-changes.outputs.changed_files }}" | |
| if [ -z "$FILES" ]; then | |
| echo "No files changed." | |
| echo "is_valid=true" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| ALL_VALID=true | |
| VALIDATION_LOG="" | |
| read -r -a FILES_ARRAY <<< "$FILES" | |
| for file_path in "${FILES_ARRAY[@]}"; do | |
| if [[ -f "$file_path" ]] && [[ "$file_path" == *.json ]]; then | |
| echo "=== Validating: $file_path ===" | |
| if ! python3 -m json.tool "$file_path" > /dev/null 2>&1; then | |
| VALIDATION_LOG+="β $file_path: Invalid JSON format\n" | |
| ALL_VALID=false | |
| continue | |
| fi | |
| # Extract Record Type | |
| RECORD_TYPE=$(python3 -c "import json; f=open('$file_path'); d=json.load(f); print(d.get('record', {}).get('type', '').upper())") | |
| if [[ "$RECORD_TYPE" != "CNAME" ]] && [[ "$RECORD_TYPE" != "A" ]]; then | |
| VALIDATION_LOG+="β $file_path: Only CNAME/A allowed. Found: $RECORD_TYPE\n" | |
| ALL_VALID=false | |
| continue | |
| fi | |
| VALUE=$(python3 -c "import json; f=open('$file_path'); d=json.load(f); print(d.get('record', {}).get('value', ''))") | |
| if [[ "$RECORD_TYPE" == "CNAME" ]]; then | |
| if [[ ! "$VALUE" =~ ^[a-zA-Z0-9][a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then | |
| VALIDATION_LOG+="β $file_path: Invalid CNAME: $VALUE\n" | |
| ALL_VALID=false | |
| fi | |
| elif [[ "$RECORD_TYPE" == "A" ]]; then | |
| if [[ ! "$VALUE" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then | |
| VALIDATION_LOG+="β $file_path: Invalid IPv4: $VALUE\n" | |
| ALL_VALID=false | |
| fi | |
| fi | |
| fi | |
| done | |
| echo -e "$VALIDATION_LOG" | |
| if [ "$ALL_VALID" = true ]; then | |
| echo "is_valid=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "is_valid=false" >> $GITHUB_OUTPUT | |
| exit 1 | |
| fi | |
| deploy: | |
| needs: validate | |
| if: github.event_name == 'push' && needs.validate.outputs.is_valid == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Update Cloudflare DNS | |
| env: | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }} | |
| CHANGED_FILES: ${{ needs.validate.outputs.changed_files }} | |
| run: | | |
| python3 -m pip install requests | |
| python3 << 'EOF' | |
| import os, json, sys, requests | |
| ZONE_ID = os.getenv('CLOUDFLARE_ZONE_ID') | |
| API_TOKEN = os.getenv('CLOUDFLARE_API_TOKEN') | |
| FILES = os.getenv('CHANGED_FILES', '').split() | |
| BASE_URL = "https://api.cloudflare.com/client/v4" | |
| HEADERS = {"Authorization": f"Bearer {API_TOKEN}", "Content-Type": "application/json"} | |
| for file_path in FILES: | |
| if not file_path.endswith('.json') or not os.path.exists(file_path): continue | |
| with open(file_path) as f: | |
| config = json.load(f) | |
| sub = config.get('subdomain', '').strip() | |
| record = config.get('record', {}) | |
| name = "codesigmax.com" if sub == "@" else f"{sub}.codesigmax.com" | |
| print(f"Processing: {name}") | |
| # Check existing | |
| res = requests.get(f"{BASE_URL}/zones/{ZONE_ID}/dns_records", headers=HEADERS, params={"name": name, "type": record.get('type')}) | |
| existing = res.json().get('result', []) | |
| payload = { | |
| "type": record.get('type'), | |
| "name": name, | |
| "content": record.get('value'), | |
| "ttl": record.get('ttl', 3600), | |
| "proxied": record.get('proxied', record.get('type') == 'A') | |
| } | |
| if existing: | |
| rid = existing[0]['id'] | |
| r = requests.put(f"{BASE_URL}/zones/{ZONE_ID}/dns_records/{rid}", headers=HEADERS, json=payload) | |
| else: | |
| r = requests.post(f"{BASE_URL}/zones/{ZONE_ID}/dns_records", headers=HEADERS, json=payload) | |
| if r.status_code in [200, 201]: | |
| print(f"β Success for {name}") | |
| else: | |
| print(f"β Error for {name}: {r.text}") | |
| EOF |