mirror of
https://github.com/blackboxprogramming/BlackRoad-Operating-System.git
synced 2026-03-17 23:34:00 -05:00
Merge branch 'main' into claude/phase1-infra-implementation-01VHNw75vk54cfUEFXYYtK9P
This commit is contained in:
63
.github/workflows/sync-cloudflare-dns.yml
vendored
Normal file
63
.github/workflows/sync-cloudflare-dns.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
name: Sync Cloudflare DNS
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- 'ops/domains.yaml'
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch: # Allow manual triggers
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
sync-dns:
|
||||||
|
name: Sync DNS Records to Cloudflare
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.10'
|
||||||
|
cache: 'pip'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
pip install -r scripts/cloudflare/requirements.txt
|
||||||
|
|
||||||
|
- name: Validate domains.yaml
|
||||||
|
run: |
|
||||||
|
python -c "import yaml; yaml.safe_load(open('ops/domains.yaml'))"
|
||||||
|
|
||||||
|
- name: Sync DNS records (dry run)
|
||||||
|
env:
|
||||||
|
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
|
||||||
|
CF_ZONE_ID: ${{ secrets.CF_ZONE_ID }}
|
||||||
|
run: |
|
||||||
|
python scripts/cloudflare/sync_dns.py --dry-run
|
||||||
|
|
||||||
|
- name: Sync DNS records
|
||||||
|
env:
|
||||||
|
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
|
||||||
|
CF_ZONE_ID: ${{ secrets.CF_ZONE_ID }}
|
||||||
|
run: |
|
||||||
|
python scripts/cloudflare/sync_dns.py
|
||||||
|
|
||||||
|
- name: Validate DNS configuration
|
||||||
|
run: |
|
||||||
|
python scripts/cloudflare/validate_dns.py --domain blackroad.systems --dns-only
|
||||||
|
continue-on-error: true # Don't fail if DNS hasn't propagated yet
|
||||||
|
|
||||||
|
- name: Comment on commit (if manual trigger)
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
github.rest.repos.createCommitComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
commit_sha: context.sha,
|
||||||
|
body: '✅ Cloudflare DNS sync completed successfully!'
|
||||||
|
})
|
||||||
@@ -57,7 +57,12 @@ RAILWAY_ENVIRONMENT_ID=00000000-0000-0000-0000-000000000000
|
|||||||
RAILWAY_DOMAIN=your-service.up.railway.app
|
RAILWAY_DOMAIN=your-service.up.railway.app
|
||||||
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/XXX/YYY/ZZZ
|
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/XXX/YYY/ZZZ
|
||||||
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/XXX/YYY
|
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/XXX/YYY
|
||||||
|
|
||||||
|
# Cloudflare DNS and CDN
|
||||||
CLOUDFLARE_API_TOKEN=cloudflare-api-token-placeholder
|
CLOUDFLARE_API_TOKEN=cloudflare-api-token-placeholder
|
||||||
|
CLOUDFLARE_ACCOUNT_ID=your-cloudflare-account-id
|
||||||
|
CLOUDFLARE_ZONE_ID=your-cloudflare-zone-id-for-blackroad-systems
|
||||||
|
CLOUDFLARE_EMAIL=your-cloudflare-email@example.com
|
||||||
|
|
||||||
# Optional cloud/API integrations
|
# Optional cloud/API integrations
|
||||||
DIGITAL_OCEAN_API_KEY=your-digital-ocean-api-key
|
DIGITAL_OCEAN_API_KEY=your-digital-ocean-api-key
|
||||||
|
|||||||
770
docs/CLOUDFLARE_MIGRATION_GUIDE.md
Normal file
770
docs/CLOUDFLARE_MIGRATION_GUIDE.md
Normal file
@@ -0,0 +1,770 @@
|
|||||||
|
# Cloudflare DNS Migration Guide
|
||||||
|
## Complete Step-by-Step Guide for BlackRoad Domains
|
||||||
|
|
||||||
|
**Version:** 1.0
|
||||||
|
**Date:** 2025-11-18
|
||||||
|
**Estimated Time:** 2-4 hours for all domains
|
||||||
|
**Skill Level:** Intermediate
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This guide walks you through migrating BlackRoad domains from GoDaddy DNS to Cloudflare DNS. The migration provides:
|
||||||
|
|
||||||
|
- ✅ **Free SSL certificates** (automatic renewal)
|
||||||
|
- ✅ **Global CDN** (faster page loads worldwide)
|
||||||
|
- ✅ **DDoS protection** (automatic threat mitigation)
|
||||||
|
- ✅ **Better DNS performance** (anycast network)
|
||||||
|
- ✅ **Advanced features** (Workers, Zero Trust, edge functions)
|
||||||
|
- ✅ **Superior analytics** (traffic insights, security events)
|
||||||
|
|
||||||
|
**What you'll need:**
|
||||||
|
- Cloudflare account (free tier is sufficient)
|
||||||
|
- GoDaddy account with domain access
|
||||||
|
- 2-4 hours of time
|
||||||
|
- Basic command line familiarity (optional, for automation)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Pre-Migration Checklist](#pre-migration-checklist)
|
||||||
|
2. [Phase 1: Set Up Cloudflare Account](#phase-1-set-up-cloudflare-account)
|
||||||
|
3. [Phase 2: Add Domains to Cloudflare](#phase-2-add-domains-to-cloudflare)
|
||||||
|
4. [Phase 3: Update Nameservers at GoDaddy](#phase-3-update-nameservers-at-godaddy)
|
||||||
|
5. [Phase 4: Configure DNS Records](#phase-4-configure-dns-records)
|
||||||
|
6. [Phase 5: Configure SSL/TLS](#phase-5-configure-ssltls)
|
||||||
|
7. [Phase 6: Optimize Performance](#phase-6-optimize-performance)
|
||||||
|
8. [Phase 7: Verify Migration](#phase-7-verify-migration)
|
||||||
|
9. [Phase 8: Automation Setup (Optional)](#phase-8-automation-setup-optional)
|
||||||
|
10. [Troubleshooting](#troubleshooting)
|
||||||
|
11. [Rollback Plan](#rollback-plan)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pre-Migration Checklist
|
||||||
|
|
||||||
|
Before starting, gather this information:
|
||||||
|
|
||||||
|
- [ ] **GoDaddy credentials** - Username and password
|
||||||
|
- [ ] **Current DNS records** - Document existing configuration
|
||||||
|
- [ ] **Email access** - For Cloudflare verification
|
||||||
|
- [ ] **Railway hostname** - Your production app URL (e.g., `your-app.up.railway.app`)
|
||||||
|
- [ ] **Current uptime** - Note if services are running properly
|
||||||
|
|
||||||
|
**⚠️ Important Notes:**
|
||||||
|
- Migration happens with **zero downtime** if done correctly
|
||||||
|
- DNS propagation takes 5-60 minutes (sometimes up to 24 hours)
|
||||||
|
- Keep GoDaddy account active (domain registration stays there)
|
||||||
|
- Only DNS management moves to Cloudflare
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Set Up Cloudflare Account
|
||||||
|
|
||||||
|
### Step 1.1: Create Cloudflare Account
|
||||||
|
|
||||||
|
1. Go to [https://dash.cloudflare.com/sign-up](https://dash.cloudflare.com/sign-up)
|
||||||
|
2. Enter your email address
|
||||||
|
3. Create a strong password
|
||||||
|
4. Verify your email address
|
||||||
|
|
||||||
|
### Step 1.2: Two-Factor Authentication (Recommended)
|
||||||
|
|
||||||
|
1. Click on your profile (top right)
|
||||||
|
2. Go to **My Profile** → **Authentication**
|
||||||
|
3. Enable **Two-Factor Authentication**
|
||||||
|
4. Save backup codes in a secure location
|
||||||
|
|
||||||
|
### Step 1.3: Get API Token
|
||||||
|
|
||||||
|
1. Go to **My Profile** → **API Tokens**
|
||||||
|
2. Click **Create Token**
|
||||||
|
3. Select **Edit zone DNS** template
|
||||||
|
4. Configure permissions:
|
||||||
|
- **Zone - DNS - Edit**
|
||||||
|
- **Zone - Zone - Read**
|
||||||
|
5. Select **Specific zones** → (you'll add zones in Phase 2)
|
||||||
|
6. Click **Continue to summary** → **Create Token**
|
||||||
|
7. **Copy the token immediately** (you won't see it again!)
|
||||||
|
8. Save it securely (we'll use it later for automation)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Add Domains to Cloudflare
|
||||||
|
|
||||||
|
We'll start with the primary domain (`blackroad.systems`) and then add others.
|
||||||
|
|
||||||
|
### Step 2.1: Add blackroad.systems
|
||||||
|
|
||||||
|
1. From Cloudflare dashboard, click **Add a site**
|
||||||
|
2. Enter: `blackroad.systems`
|
||||||
|
3. Click **Add site**
|
||||||
|
4. Select **Free plan** → Click **Continue**
|
||||||
|
5. Cloudflare will scan existing DNS records from GoDaddy
|
||||||
|
6. Wait 30-60 seconds for scan to complete
|
||||||
|
|
||||||
|
### Step 2.2: Review Scanned Records
|
||||||
|
|
||||||
|
Cloudflare should detect existing records. Review them:
|
||||||
|
|
||||||
|
- ✅ Check for A records pointing to your server
|
||||||
|
- ✅ Check for CNAME records
|
||||||
|
- ✅ Check for MX records (email)
|
||||||
|
- ✅ Check for TXT records (SPF, verification)
|
||||||
|
|
||||||
|
**Common issues:**
|
||||||
|
- Some records might be missing → We'll add them manually later
|
||||||
|
- TTL values might be high → We'll adjust them
|
||||||
|
|
||||||
|
### Step 2.3: Get Nameservers
|
||||||
|
|
||||||
|
After scanning, Cloudflare will show 2 nameservers like:
|
||||||
|
|
||||||
|
```
|
||||||
|
aaaa.ns.cloudflare.com
|
||||||
|
bbbb.ns.cloudflare.com
|
||||||
|
```
|
||||||
|
|
||||||
|
**⚠️ IMPORTANT:** Copy these nameservers! You'll need them in Phase 3.
|
||||||
|
|
||||||
|
**Don't click "Done" yet** - we'll do that after updating nameservers at GoDaddy.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: Update Nameservers at GoDaddy
|
||||||
|
|
||||||
|
**⏰ Estimated Time:** 10 minutes + 5-60 minutes propagation
|
||||||
|
|
||||||
|
### Step 3.1: Log in to GoDaddy
|
||||||
|
|
||||||
|
1. Go to [https://account.godaddy.com](https://account.godaddy.com)
|
||||||
|
2. Sign in with your credentials
|
||||||
|
3. Go to **My Products** → **Domains**
|
||||||
|
|
||||||
|
### Step 3.2: Update Nameservers for blackroad.systems
|
||||||
|
|
||||||
|
1. Find `blackroad.systems` in your domain list
|
||||||
|
2. Click the three dots (...) → **Manage DNS**
|
||||||
|
3. Scroll down to **Nameservers** section
|
||||||
|
4. Click **Change** → Select **Enter my own nameservers (advanced)**
|
||||||
|
5. Remove existing nameservers
|
||||||
|
6. Add the 2 Cloudflare nameservers from Phase 2, Step 2.3:
|
||||||
|
```
|
||||||
|
aaaa.ns.cloudflare.com
|
||||||
|
bbbb.ns.cloudflare.com
|
||||||
|
```
|
||||||
|
7. Click **Save**
|
||||||
|
8. Confirm the change
|
||||||
|
|
||||||
|
**What happens now:**
|
||||||
|
- GoDaddy will propagate the nameserver change
|
||||||
|
- This takes 5-60 minutes (sometimes up to 24 hours)
|
||||||
|
- Your site will continue working during this time
|
||||||
|
|
||||||
|
### Step 3.3: Return to Cloudflare
|
||||||
|
|
||||||
|
1. Go back to Cloudflare dashboard
|
||||||
|
2. Click **Done, check nameservers**
|
||||||
|
3. Cloudflare will start checking for nameserver changes
|
||||||
|
4. You'll see status: **Pending Nameserver Update**
|
||||||
|
|
||||||
|
**⏳ Wait time:** 5-60 minutes for Cloudflare to detect the change
|
||||||
|
|
||||||
|
You can check status by:
|
||||||
|
- Refreshing the Cloudflare dashboard
|
||||||
|
- Running: `dig NS blackroad.systems` (should show Cloudflare nameservers)
|
||||||
|
- Using: [https://dnschecker.org](https://dnschecker.org)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: Configure DNS Records
|
||||||
|
|
||||||
|
Once Cloudflare shows **Active** status, configure DNS records.
|
||||||
|
|
||||||
|
### Step 4.1: Verify Existing Records
|
||||||
|
|
||||||
|
1. In Cloudflare dashboard, go to **DNS** → **Records**
|
||||||
|
2. Review scanned records
|
||||||
|
3. Remove any incorrect or outdated records
|
||||||
|
|
||||||
|
### Step 4.2: Add/Update Primary Records
|
||||||
|
|
||||||
|
#### Root Domain (blackroad.systems)
|
||||||
|
|
||||||
|
| Type | Name | Target | Proxy | TTL |
|
||||||
|
|------|------|--------|-------|-----|
|
||||||
|
| CNAME | @ | `your-app.up.railway.app` | ✅ Proxied | Auto |
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
1. Click **Add record**
|
||||||
|
2. Type: **CNAME**
|
||||||
|
3. Name: **@** (represents root domain)
|
||||||
|
4. Target: **your-railway-app.up.railway.app** (replace with actual Railway URL)
|
||||||
|
5. Proxy status: **Proxied** (orange cloud icon)
|
||||||
|
6. TTL: **Auto**
|
||||||
|
7. Click **Save**
|
||||||
|
|
||||||
|
#### WWW Subdomain
|
||||||
|
|
||||||
|
| Type | Name | Target | Proxy | TTL |
|
||||||
|
|------|------|--------|-------|-----|
|
||||||
|
| CNAME | www | blackroad.systems | ✅ Proxied | Auto |
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
1. Click **Add record**
|
||||||
|
2. Type: **CNAME**
|
||||||
|
3. Name: **www**
|
||||||
|
4. Target: **blackroad.systems**
|
||||||
|
5. Proxy status: **Proxied**
|
||||||
|
6. Click **Save**
|
||||||
|
|
||||||
|
#### API Subdomain
|
||||||
|
|
||||||
|
| Type | Name | Target | Proxy | TTL |
|
||||||
|
|------|------|--------|-------|-----|
|
||||||
|
| CNAME | api | `your-app.up.railway.app` | ✅ Proxied | Auto |
|
||||||
|
|
||||||
|
#### OS Subdomain
|
||||||
|
|
||||||
|
| Type | Name | Target | Proxy | TTL |
|
||||||
|
|------|------|--------|-------|-----|
|
||||||
|
| CNAME | os | blackroad.systems | ✅ Proxied | Auto |
|
||||||
|
|
||||||
|
### Step 4.3: Configure Email Records (If Applicable)
|
||||||
|
|
||||||
|
If you use Google Workspace, G Suite, or custom email:
|
||||||
|
|
||||||
|
#### SPF Record
|
||||||
|
| Type | Name | Content | TTL |
|
||||||
|
|------|------|---------|-----|
|
||||||
|
| TXT | @ | `v=spf1 include:_spf.google.com ~all` | Auto |
|
||||||
|
|
||||||
|
#### MX Records
|
||||||
|
| Type | Name | Content | Priority | TTL |
|
||||||
|
|------|------|---------|----------|-----|
|
||||||
|
| MX | @ | aspmx.l.google.com | 1 | Auto |
|
||||||
|
| MX | @ | alt1.aspmx.l.google.com | 5 | Auto |
|
||||||
|
| MX | @ | alt2.aspmx.l.google.com | 5 | Auto |
|
||||||
|
|
||||||
|
### Step 4.4: Verify Records
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check DNS resolution
|
||||||
|
dig blackroad.systems
|
||||||
|
dig www.blackroad.systems
|
||||||
|
dig api.blackroad.systems
|
||||||
|
|
||||||
|
# Or use Cloudflare dashboard DNS checker
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 5: Configure SSL/TLS
|
||||||
|
|
||||||
|
### Step 5.1: Set Encryption Mode
|
||||||
|
|
||||||
|
1. In Cloudflare dashboard, go to **SSL/TLS**
|
||||||
|
2. Set **Encryption mode** to **Full (strict)**
|
||||||
|
- This ensures encryption between Cloudflare and Railway
|
||||||
|
- Railway automatically provides SSL certificates
|
||||||
|
|
||||||
|
**⚠️ Important:** Do NOT use "Flexible" mode (insecure)
|
||||||
|
|
||||||
|
### Step 5.2: Enable Always Use HTTPS
|
||||||
|
|
||||||
|
1. Go to **SSL/TLS** → **Edge Certificates**
|
||||||
|
2. Enable **Always Use HTTPS**
|
||||||
|
- This redirects all HTTP traffic to HTTPS
|
||||||
|
3. Enable **Automatic HTTPS Rewrites**
|
||||||
|
- Fixes mixed content warnings
|
||||||
|
|
||||||
|
### Step 5.3: Enable HSTS (Optional but Recommended)
|
||||||
|
|
||||||
|
1. Still in **Edge Certificates**
|
||||||
|
2. Enable **HTTP Strict Transport Security (HSTS)**
|
||||||
|
3. Configuration:
|
||||||
|
- **Max Age:** 6 months (15768000 seconds)
|
||||||
|
- **Include subdomains:** ✅ Enabled
|
||||||
|
- **Preload:** ❌ Disabled (enable later when stable)
|
||||||
|
- **No-Sniff header:** ✅ Enabled
|
||||||
|
|
||||||
|
**⚠️ Warning:** HSTS is irreversible for the max-age period. Only enable when confident.
|
||||||
|
|
||||||
|
### Step 5.4: Enable TLS 1.3
|
||||||
|
|
||||||
|
1. Go to **SSL/TLS** → **Edge Certificates**
|
||||||
|
2. **Minimum TLS Version:** Set to **TLS 1.2** (or 1.3 if supported)
|
||||||
|
3. **TLS 1.3:** ✅ Enabled
|
||||||
|
|
||||||
|
### Step 5.5: Verify SSL Configuration
|
||||||
|
|
||||||
|
1. Visit: `https://blackroad.systems`
|
||||||
|
2. Click the padlock icon in browser
|
||||||
|
3. Verify certificate is valid and issued by Cloudflare
|
||||||
|
4. Check expiry date (should auto-renew)
|
||||||
|
|
||||||
|
**Test with SSL Labs:**
|
||||||
|
```
|
||||||
|
https://www.ssllabs.com/ssltest/analyze.html?d=blackroad.systems
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 6: Optimize Performance
|
||||||
|
|
||||||
|
### Step 6.1: Configure Caching
|
||||||
|
|
||||||
|
1. Go to **Caching** → **Configuration**
|
||||||
|
2. **Caching Level:** Standard
|
||||||
|
3. **Browser Cache TTL:** Respect Existing Headers
|
||||||
|
|
||||||
|
### Step 6.2: Enable Auto Minify
|
||||||
|
|
||||||
|
1. Go to **Speed** → **Optimization**
|
||||||
|
2. Enable **Auto Minify**:
|
||||||
|
- ✅ JavaScript
|
||||||
|
- ✅ CSS
|
||||||
|
- ✅ HTML
|
||||||
|
|
||||||
|
### Step 6.3: Enable Brotli Compression
|
||||||
|
|
||||||
|
1. Still in **Speed** → **Optimization**
|
||||||
|
2. Enable **Brotli** compression (better than gzip)
|
||||||
|
|
||||||
|
### Step 6.4: Create Page Rules
|
||||||
|
|
||||||
|
1. Go to **Rules** → **Page Rules**
|
||||||
|
2. Create rule for API bypass:
|
||||||
|
|
||||||
|
**Rule 1: API Cache Bypass**
|
||||||
|
```
|
||||||
|
URL: *blackroad.systems/api/*
|
||||||
|
Settings:
|
||||||
|
- Cache Level: Bypass
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rule 2: WWW Redirect**
|
||||||
|
```
|
||||||
|
URL: www.blackroad.systems/*
|
||||||
|
Settings:
|
||||||
|
- Forwarding URL: 301 redirect to https://blackroad.systems/$1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Free plan allows 3 page rules. Use them wisely!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 7: Verify Migration
|
||||||
|
|
||||||
|
### Step 7.1: DNS Verification
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check DNS propagation
|
||||||
|
dig blackroad.systems
|
||||||
|
|
||||||
|
# Check with multiple tools
|
||||||
|
dig @8.8.8.8 blackroad.systems
|
||||||
|
dig @1.1.1.1 blackroad.systems
|
||||||
|
|
||||||
|
# Or use online tool
|
||||||
|
# https://dnschecker.org
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected results:**
|
||||||
|
- Should resolve to Cloudflare IP addresses
|
||||||
|
- CNAME records should point to Railway
|
||||||
|
- Nameservers should be Cloudflare
|
||||||
|
|
||||||
|
### Step 7.2: HTTP/HTTPS Verification
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test HTTP → HTTPS redirect
|
||||||
|
curl -I http://blackroad.systems
|
||||||
|
# Should return: 301 Moved Permanently
|
||||||
|
# Location: https://blackroad.systems/
|
||||||
|
|
||||||
|
# Test HTTPS
|
||||||
|
curl -I https://blackroad.systems
|
||||||
|
# Should return: 200 OK
|
||||||
|
|
||||||
|
# Test WWW → apex redirect
|
||||||
|
curl -I https://www.blackroad.systems
|
||||||
|
# Should redirect to https://blackroad.systems
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 7.3: SSL Certificate Check
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check SSL certificate
|
||||||
|
openssl s_client -connect blackroad.systems:443 -servername blackroad.systems
|
||||||
|
|
||||||
|
# Look for:
|
||||||
|
# - Issuer: Cloudflare
|
||||||
|
# - Valid dates
|
||||||
|
# - No errors
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 7.4: Application Functionality
|
||||||
|
|
||||||
|
1. Visit `https://blackroad.systems`
|
||||||
|
2. Test all major features:
|
||||||
|
- [ ] Page loads correctly
|
||||||
|
- [ ] No mixed content warnings
|
||||||
|
- [ ] API calls work
|
||||||
|
- [ ] Authentication works
|
||||||
|
- [ ] Static assets load (CSS, JS, images)
|
||||||
|
|
||||||
|
### Step 7.5: Automated Validation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Use the validation script
|
||||||
|
cd /path/to/BlackRoad-Operating-System
|
||||||
|
python scripts/cloudflare/validate_dns.py --domain blackroad.systems
|
||||||
|
|
||||||
|
# This checks:
|
||||||
|
# - DNS resolution
|
||||||
|
# - SSL certificate validity
|
||||||
|
# - HTTP accessibility
|
||||||
|
# - Redirect configuration
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 8: Automation Setup (Optional)
|
||||||
|
|
||||||
|
### Step 8.1: Install Script Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Navigate to project
|
||||||
|
cd /path/to/BlackRoad-Operating-System
|
||||||
|
|
||||||
|
# Install Python dependencies
|
||||||
|
pip install -r scripts/cloudflare/requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 8.2: Set Up Environment Variables
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create .env file (DO NOT COMMIT)
|
||||||
|
cat >> .env << EOF
|
||||||
|
CF_API_TOKEN=your-cloudflare-api-token
|
||||||
|
CF_ZONE_ID=your-zone-id
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Or add to shell profile
|
||||||
|
echo 'export CF_API_TOKEN="your-token"' >> ~/.bashrc
|
||||||
|
echo 'export CF_ZONE_ID="your-zone-id"' >> ~/.bashrc
|
||||||
|
source ~/.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 8.3: Update domains.yaml
|
||||||
|
|
||||||
|
Edit `ops/domains.yaml` to reflect your Cloudflare configuration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
domains:
|
||||||
|
- name: "blackroad.systems"
|
||||||
|
type: "root"
|
||||||
|
provider: "cloudflare"
|
||||||
|
mode: "dns"
|
||||||
|
record:
|
||||||
|
type: "CNAME"
|
||||||
|
value: "your-actual-railway-app.up.railway.app"
|
||||||
|
proxied: true
|
||||||
|
|
||||||
|
- name: "blackroad.ai"
|
||||||
|
type: "root"
|
||||||
|
provider: "cloudflare"
|
||||||
|
mode: "dns"
|
||||||
|
record:
|
||||||
|
type: "CNAME"
|
||||||
|
value: "blackroad.systems"
|
||||||
|
proxied: true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 8.4: Test Automation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Dry run (shows what would change)
|
||||||
|
python scripts/cloudflare/sync_dns.py --dry-run
|
||||||
|
|
||||||
|
# Apply changes
|
||||||
|
python scripts/cloudflare/sync_dns.py
|
||||||
|
|
||||||
|
# Validate
|
||||||
|
python scripts/cloudflare/validate_dns.py --all
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 8.5: Set Up GitHub Actions (Optional)
|
||||||
|
|
||||||
|
Add secrets to GitHub:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gh secret set CF_API_TOKEN
|
||||||
|
gh secret set CF_ZONE_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
Create workflow file (`.github/workflows/sync-cloudflare-dns.yml`):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Sync Cloudflare DNS
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- 'ops/domains.yaml'
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
sync:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.10'
|
||||||
|
- run: pip install -r scripts/cloudflare/requirements.txt
|
||||||
|
- run: python scripts/cloudflare/sync_dns.py
|
||||||
|
env:
|
||||||
|
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
|
||||||
|
CF_ZONE_ID: ${{ secrets.CF_ZONE_ID }}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Issue: DNS Not Resolving
|
||||||
|
|
||||||
|
**Symptoms:** `dig blackroad.systems` returns no results
|
||||||
|
|
||||||
|
**Causes:**
|
||||||
|
- Nameservers not updated at GoDaddy
|
||||||
|
- DNS propagation not complete
|
||||||
|
- Records not configured in Cloudflare
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. Check nameservers at GoDaddy
|
||||||
|
2. Wait 5-60 minutes for propagation
|
||||||
|
3. Check Cloudflare zone status (should be "Active")
|
||||||
|
4. Verify DNS records exist in Cloudflare
|
||||||
|
|
||||||
|
### Issue: SSL Certificate Errors
|
||||||
|
|
||||||
|
**Symptoms:** Browser shows "Not Secure" or certificate warning
|
||||||
|
|
||||||
|
**Causes:**
|
||||||
|
- SSL/TLS mode incorrect
|
||||||
|
- Railway app doesn't have valid certificate
|
||||||
|
- Certificate provisioning in progress
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. Set SSL/TLS mode to "Full (strict)"
|
||||||
|
2. Verify Railway app has SSL
|
||||||
|
3. Wait 5-10 minutes for certificate provisioning
|
||||||
|
4. Clear browser cache and retry
|
||||||
|
|
||||||
|
### Issue: Site Not Loading (403/502 Errors)
|
||||||
|
|
||||||
|
**Symptoms:** Site returns 403 Forbidden or 502 Bad Gateway
|
||||||
|
|
||||||
|
**Causes:**
|
||||||
|
- Railway app not running
|
||||||
|
- Incorrect CNAME target
|
||||||
|
- Cloudflare firewall blocking
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. Check Railway app status and logs
|
||||||
|
2. Verify CNAME points to correct Railway URL
|
||||||
|
3. Check Cloudflare firewall rules
|
||||||
|
4. Disable Cloudflare proxy temporarily (DNS-only) to test
|
||||||
|
|
||||||
|
### Issue: Mixed Content Warnings
|
||||||
|
|
||||||
|
**Symptoms:** Some assets load as insecure (http://)
|
||||||
|
|
||||||
|
**Causes:**
|
||||||
|
- Hard-coded HTTP URLs in code
|
||||||
|
- External resources using HTTP
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. Enable "Automatic HTTPS Rewrites" in Cloudflare
|
||||||
|
2. Update hard-coded URLs to HTTPS
|
||||||
|
3. Use protocol-relative URLs: `//example.com/asset.js`
|
||||||
|
|
||||||
|
### Issue: Email Not Working
|
||||||
|
|
||||||
|
**Symptoms:** Emails not sending/receiving
|
||||||
|
|
||||||
|
**Causes:**
|
||||||
|
- MX records not migrated
|
||||||
|
- SPF/DKIM records missing
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. Add MX records in Cloudflare (from Phase 4.3)
|
||||||
|
2. Add SPF TXT record
|
||||||
|
3. Add DKIM records if using custom email
|
||||||
|
4. Verify with: `dig MX blackroad.systems`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rollback Plan
|
||||||
|
|
||||||
|
If you need to revert to GoDaddy DNS:
|
||||||
|
|
||||||
|
### Quick Rollback
|
||||||
|
|
||||||
|
1. Go to GoDaddy → Domains → blackroad.systems
|
||||||
|
2. Nameservers → Change to **GoDaddy defaults**
|
||||||
|
3. Wait 5-60 minutes for propagation
|
||||||
|
4. Site will revert to GoDaddy DNS
|
||||||
|
|
||||||
|
**⚠️ Note:** You can keep the Cloudflare account and try again later.
|
||||||
|
|
||||||
|
### Gradual Rollback
|
||||||
|
|
||||||
|
If experiencing issues but want to keep trying:
|
||||||
|
|
||||||
|
1. In Cloudflare, change proxy status to **DNS Only** (gray cloud)
|
||||||
|
2. This bypasses Cloudflare's proxy but keeps DNS
|
||||||
|
3. Troubleshoot issues
|
||||||
|
4. Re-enable proxy when fixed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps After Migration
|
||||||
|
|
||||||
|
### For All Remaining Domains
|
||||||
|
|
||||||
|
Repeat the process for:
|
||||||
|
- blackroad.ai
|
||||||
|
- blackroad.network
|
||||||
|
- blackroad.me
|
||||||
|
- lucidia.earth
|
||||||
|
- aliceqi.com
|
||||||
|
- blackroadqi.com
|
||||||
|
- roadwallet.com
|
||||||
|
- aliceos.io
|
||||||
|
- blackroadquantum.com
|
||||||
|
|
||||||
|
**Pro tip:** After doing blackroad.systems, the others are easier!
|
||||||
|
|
||||||
|
### Monitoring and Maintenance
|
||||||
|
|
||||||
|
1. **Set up monitoring:**
|
||||||
|
- Uptime monitoring (UptimeRobot, Pingdom)
|
||||||
|
- SSL certificate expiry monitoring
|
||||||
|
- Performance monitoring (Cloudflare Analytics)
|
||||||
|
|
||||||
|
2. **Review quarterly:**
|
||||||
|
- DNS records (remove unused)
|
||||||
|
- Page rules and caching
|
||||||
|
- Security settings
|
||||||
|
- Analytics and performance
|
||||||
|
|
||||||
|
3. **Stay updated:**
|
||||||
|
- Review Cloudflare changelog
|
||||||
|
- Test new features in sandbox
|
||||||
|
- Keep API tokens rotated
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Migration Checklist
|
||||||
|
|
||||||
|
Use this to track your progress:
|
||||||
|
|
||||||
|
### Pre-Migration
|
||||||
|
- [ ] GoDaddy credentials ready
|
||||||
|
- [ ] Current DNS records documented
|
||||||
|
- [ ] Railway hostname confirmed
|
||||||
|
- [ ] Cloudflare account created
|
||||||
|
|
||||||
|
### Cloudflare Setup
|
||||||
|
- [ ] API token generated and saved
|
||||||
|
- [ ] Domain added to Cloudflare
|
||||||
|
- [ ] DNS records scanned
|
||||||
|
- [ ] Nameservers noted
|
||||||
|
|
||||||
|
### GoDaddy Update
|
||||||
|
- [ ] Nameservers updated at GoDaddy
|
||||||
|
- [ ] Change confirmed
|
||||||
|
- [ ] Propagation completed (zone shows "Active")
|
||||||
|
|
||||||
|
### DNS Configuration
|
||||||
|
- [ ] Root domain CNAME added
|
||||||
|
- [ ] WWW subdomain added
|
||||||
|
- [ ] API subdomain added
|
||||||
|
- [ ] OS subdomain added
|
||||||
|
- [ ] Email records added (if applicable)
|
||||||
|
|
||||||
|
### SSL/TLS
|
||||||
|
- [ ] Encryption mode set to Full (strict)
|
||||||
|
- [ ] Always Use HTTPS enabled
|
||||||
|
- [ ] Automatic HTTPS Rewrites enabled
|
||||||
|
- [ ] HSTS configured (optional)
|
||||||
|
- [ ] TLS 1.3 enabled
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
- [ ] Auto Minify enabled
|
||||||
|
- [ ] Brotli compression enabled
|
||||||
|
- [ ] Page rules configured
|
||||||
|
- [ ] Caching configured
|
||||||
|
|
||||||
|
### Verification
|
||||||
|
- [ ] DNS resolution verified
|
||||||
|
- [ ] SSL certificate valid
|
||||||
|
- [ ] HTTP → HTTPS redirect working
|
||||||
|
- [ ] WWW → apex redirect working
|
||||||
|
- [ ] Site accessible and functional
|
||||||
|
- [ ] API endpoints working
|
||||||
|
- [ ] Email working (if applicable)
|
||||||
|
|
||||||
|
### Automation (Optional)
|
||||||
|
- [ ] Python dependencies installed
|
||||||
|
- [ ] Environment variables set
|
||||||
|
- [ ] domains.yaml updated
|
||||||
|
- [ ] Automation scripts tested
|
||||||
|
- [ ] GitHub Actions configured (optional)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- [Cloudflare DNS Blueprint](../infra/cloudflare/CLOUDFLARE_DNS_BLUEPRINT.md)
|
||||||
|
- [Scripts README](../scripts/cloudflare/README.md)
|
||||||
|
- [Domain Configuration](../ops/domains.yaml)
|
||||||
|
|
||||||
|
### External Links
|
||||||
|
- [Cloudflare Dashboard](https://dash.cloudflare.com)
|
||||||
|
- [Cloudflare API Docs](https://developers.cloudflare.com/api/)
|
||||||
|
- [DNS Checker Tool](https://dnschecker.org)
|
||||||
|
- [SSL Labs Test](https://www.ssllabs.com/ssltest/)
|
||||||
|
- [Railway Dashboard](https://railway.app/dashboard)
|
||||||
|
|
||||||
|
### Support
|
||||||
|
- Cloudflare Community: https://community.cloudflare.com/
|
||||||
|
- Railway Discord: https://discord.gg/railway
|
||||||
|
- BlackRoad GitHub Issues: https://github.com/blackboxprogramming/BlackRoad-Operating-System/issues
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Congratulations!** 🎉
|
||||||
|
|
||||||
|
You've successfully migrated to Cloudflare DNS. Your sites now benefit from:
|
||||||
|
- Global CDN and faster performance
|
||||||
|
- Free SSL with auto-renewal
|
||||||
|
- DDoS protection
|
||||||
|
- Advanced security features
|
||||||
|
- Better analytics and insights
|
||||||
|
|
||||||
|
**Questions or issues?** Check the troubleshooting section or open a GitHub issue.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated:** 2025-11-18
|
||||||
|
**Maintainer:** BlackRoad DevOps Team
|
||||||
|
**Version:** 1.0
|
||||||
313
scripts/cloudflare/README.md
Normal file
313
scripts/cloudflare/README.md
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
# Cloudflare DNS Management Scripts
|
||||||
|
|
||||||
|
This directory contains automation scripts for managing BlackRoad domains via the Cloudflare API.
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
|
||||||
|
### `sync_dns.py`
|
||||||
|
Synchronizes DNS records from `ops/domains.yaml` to Cloudflare. Handles creating new records and updating existing ones.
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Automated DNS record synchronization
|
||||||
|
- Dry-run mode to preview changes
|
||||||
|
- Colored output for easy scanning
|
||||||
|
- Support for multiple record types (A, CNAME, MX, TXT)
|
||||||
|
- Automatic proxying configuration
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```bash
|
||||||
|
# Set environment variables
|
||||||
|
export CF_API_TOKEN="your-cloudflare-api-token"
|
||||||
|
export CF_ZONE_ID="your-zone-id"
|
||||||
|
|
||||||
|
# Preview changes (dry run)
|
||||||
|
python scripts/cloudflare/sync_dns.py --dry-run
|
||||||
|
|
||||||
|
# Apply changes
|
||||||
|
python scripts/cloudflare/sync_dns.py
|
||||||
|
|
||||||
|
# Or with command-line arguments
|
||||||
|
python scripts/cloudflare/sync_dns.py \
|
||||||
|
--token "your-token" \
|
||||||
|
--zone-id "your-zone-id" \
|
||||||
|
--zone-name "blackroad.systems"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `validate_dns.py`
|
||||||
|
Validates DNS configuration and checks propagation status across the internet.
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- DNS resolution verification
|
||||||
|
- SSL certificate validation
|
||||||
|
- HTTP/HTTPS accessibility testing
|
||||||
|
- Redirect verification (www → apex, HTTP → HTTPS)
|
||||||
|
- Support for checking multiple domains
|
||||||
|
|
||||||
|
**Usage:**
|
||||||
|
```bash
|
||||||
|
# Check single domain (default: blackroad.systems)
|
||||||
|
python scripts/cloudflare/validate_dns.py
|
||||||
|
|
||||||
|
# Check specific domain
|
||||||
|
python scripts/cloudflare/validate_dns.py --domain blackroad.ai
|
||||||
|
|
||||||
|
# Check all BlackRoad domains
|
||||||
|
python scripts/cloudflare/validate_dns.py --all
|
||||||
|
|
||||||
|
# DNS-only check (skip SSL and HTTP)
|
||||||
|
python scripts/cloudflare/validate_dns.py --dns-only
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- Python 3.8 or higher
|
||||||
|
- Cloudflare account with API token
|
||||||
|
- Domain(s) added to Cloudflare
|
||||||
|
|
||||||
|
### Install Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install required packages
|
||||||
|
pip install -r scripts/cloudflare/requirements.txt
|
||||||
|
|
||||||
|
# Or install individually
|
||||||
|
pip install requests pyyaml dnspython colorama
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Getting Cloudflare API Token
|
||||||
|
|
||||||
|
1. Log in to [Cloudflare Dashboard](https://dash.cloudflare.com)
|
||||||
|
2. Go to **My Profile** → **API Tokens**
|
||||||
|
3. Click **Create Token**
|
||||||
|
4. Use the **Edit zone DNS** template
|
||||||
|
5. Select the zones you want to manage
|
||||||
|
6. Create token and copy it
|
||||||
|
|
||||||
|
### Getting Zone ID
|
||||||
|
|
||||||
|
1. Go to [Cloudflare Dashboard](https://dash.cloudflare.com)
|
||||||
|
2. Select your domain (e.g., `blackroad.systems`)
|
||||||
|
3. Scroll down to **API** section on the right sidebar
|
||||||
|
4. Copy the **Zone ID**
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Add to your shell profile (~/.bashrc, ~/.zshrc, etc.)
|
||||||
|
export CF_API_TOKEN="your-cloudflare-api-token-here"
|
||||||
|
export CF_ZONE_ID="your-zone-id-here"
|
||||||
|
|
||||||
|
# Or create a .env file (DO NOT COMMIT THIS)
|
||||||
|
echo "CF_API_TOKEN=your-token" >> .env
|
||||||
|
echo "CF_ZONE_ID=your-zone-id" >> .env
|
||||||
|
source .env
|
||||||
|
```
|
||||||
|
|
||||||
|
## Domain Configuration
|
||||||
|
|
||||||
|
DNS records are defined in `ops/domains.yaml`. Example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
domains:
|
||||||
|
- name: "blackroad.systems"
|
||||||
|
type: "root"
|
||||||
|
provider: "cloudflare"
|
||||||
|
mode: "dns"
|
||||||
|
record:
|
||||||
|
type: "CNAME"
|
||||||
|
value: "blackroad-os-production.up.railway.app"
|
||||||
|
ttl: 1 # Auto
|
||||||
|
proxied: true
|
||||||
|
|
||||||
|
- name: "api.blackroad.systems"
|
||||||
|
type: "subdomain"
|
||||||
|
provider: "cloudflare"
|
||||||
|
mode: "dns"
|
||||||
|
record:
|
||||||
|
type: "CNAME"
|
||||||
|
value: "blackroad-os-production.up.railway.app"
|
||||||
|
proxied: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
### Initial Migration
|
||||||
|
|
||||||
|
1. **Add domain to Cloudflare** (manual step via dashboard)
|
||||||
|
```
|
||||||
|
- Go to Cloudflare → Add a site
|
||||||
|
- Enter domain name
|
||||||
|
- Choose Free plan
|
||||||
|
- Follow setup wizard
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Update nameservers at registrar** (GoDaddy, etc.)
|
||||||
|
```
|
||||||
|
- Copy nameservers from Cloudflare
|
||||||
|
- Update at domain registrar
|
||||||
|
- Wait 5-60 minutes for propagation
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Configure DNS records**
|
||||||
|
```bash
|
||||||
|
# Update ops/domains.yaml with your records
|
||||||
|
|
||||||
|
# Preview changes
|
||||||
|
python scripts/cloudflare/sync_dns.py --dry-run
|
||||||
|
|
||||||
|
# Apply changes
|
||||||
|
python scripts/cloudflare/sync_dns.py
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Verify configuration**
|
||||||
|
```bash
|
||||||
|
# Check DNS propagation
|
||||||
|
python scripts/cloudflare/validate_dns.py
|
||||||
|
|
||||||
|
# Or check specific domain
|
||||||
|
python scripts/cloudflare/validate_dns.py --domain blackroad.systems
|
||||||
|
```
|
||||||
|
|
||||||
|
### Regular Updates
|
||||||
|
|
||||||
|
When updating DNS records:
|
||||||
|
|
||||||
|
1. Edit `ops/domains.yaml`
|
||||||
|
2. Run dry-run to preview: `python scripts/cloudflare/sync_dns.py --dry-run`
|
||||||
|
3. Apply changes: `python scripts/cloudflare/sync_dns.py`
|
||||||
|
4. Validate: `python scripts/cloudflare/validate_dns.py`
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### DNS Not Resolving
|
||||||
|
|
||||||
|
**Problem:** Domain doesn't resolve
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
```bash
|
||||||
|
# Check DNS with dig
|
||||||
|
dig blackroad.systems
|
||||||
|
|
||||||
|
# Check with validation script
|
||||||
|
python scripts/cloudflare/validate_dns.py --domain blackroad.systems
|
||||||
|
|
||||||
|
# Wait for propagation (5-60 minutes after nameserver change)
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Authentication Errors
|
||||||
|
|
||||||
|
**Problem:** `401 Unauthorized` or `403 Forbidden`
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
- Verify API token is correct
|
||||||
|
- Check token has "Edit DNS" permission for the zone
|
||||||
|
- Ensure token hasn't expired
|
||||||
|
- Verify zone ID is correct
|
||||||
|
|
||||||
|
### Script Errors
|
||||||
|
|
||||||
|
**Problem:** Import errors or missing dependencies
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
```bash
|
||||||
|
# Install all dependencies
|
||||||
|
pip install -r scripts/cloudflare/requirements.txt
|
||||||
|
|
||||||
|
# Or install missing package
|
||||||
|
pip install <package-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration Drift
|
||||||
|
|
||||||
|
**Problem:** Cloudflare records don't match `domains.yaml`
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
```bash
|
||||||
|
# Run sync to update Cloudflare to match config
|
||||||
|
python scripts/cloudflare/sync_dns.py
|
||||||
|
|
||||||
|
# Or manually update records in Cloudflare dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Best Practices
|
||||||
|
|
||||||
|
1. **Never commit API tokens**
|
||||||
|
- Add `.env` to `.gitignore`
|
||||||
|
- Use environment variables
|
||||||
|
- Rotate tokens periodically
|
||||||
|
|
||||||
|
2. **Use scoped tokens**
|
||||||
|
- Create tokens with minimum required permissions
|
||||||
|
- Use zone-specific tokens when possible
|
||||||
|
- Avoid using Global API Key
|
||||||
|
|
||||||
|
3. **Audit regularly**
|
||||||
|
- Review DNS records monthly
|
||||||
|
- Check token usage in Cloudflare dashboard
|
||||||
|
- Remove unused tokens
|
||||||
|
|
||||||
|
## Integration with CI/CD
|
||||||
|
|
||||||
|
### GitHub Actions Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Sync DNS Records
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- 'ops/domains.yaml'
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
sync-dns:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.10'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
pip install -r scripts/cloudflare/requirements.txt
|
||||||
|
|
||||||
|
- name: Sync DNS records
|
||||||
|
env:
|
||||||
|
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
|
||||||
|
CF_ZONE_ID: ${{ secrets.CF_ZONE_ID }}
|
||||||
|
run: |
|
||||||
|
python scripts/cloudflare/sync_dns.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Add secrets to GitHub:
|
||||||
|
```bash
|
||||||
|
gh secret set CF_API_TOKEN
|
||||||
|
gh secret set CF_ZONE_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
## Additional Resources
|
||||||
|
|
||||||
|
- [Cloudflare API Documentation](https://developers.cloudflare.com/api/)
|
||||||
|
- [Cloudflare DNS Documentation](https://developers.cloudflare.com/dns/)
|
||||||
|
- [DNS Blueprint](../../infra/cloudflare/CLOUDFLARE_DNS_BLUEPRINT.md)
|
||||||
|
- [Domain Configuration](../../ops/domains.yaml)
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
For issues or questions:
|
||||||
|
- Check the [CLOUDFLARE_DNS_BLUEPRINT.md](../../infra/cloudflare/CLOUDFLARE_DNS_BLUEPRINT.md)
|
||||||
|
- Review Cloudflare dashboard for zone status
|
||||||
|
- Check script output for error messages
|
||||||
|
- Verify API token permissions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Last Updated:** 2025-11-18
|
||||||
|
**Maintained by:** BlackRoad DevOps Team
|
||||||
14
scripts/cloudflare/requirements.txt
Normal file
14
scripts/cloudflare/requirements.txt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Cloudflare DNS Management Scripts Requirements
|
||||||
|
# Install with: pip install -r scripts/cloudflare/requirements.txt
|
||||||
|
|
||||||
|
# HTTP client for API requests
|
||||||
|
requests>=2.31.0
|
||||||
|
|
||||||
|
# YAML parsing for domains.yaml
|
||||||
|
PyYAML>=6.0.1
|
||||||
|
|
||||||
|
# DNS resolution verification
|
||||||
|
dnspython>=2.4.2
|
||||||
|
|
||||||
|
# Colored terminal output
|
||||||
|
colorama>=0.4.6
|
||||||
359
scripts/cloudflare/sync_dns.py
Executable file
359
scripts/cloudflare/sync_dns.py
Executable file
@@ -0,0 +1,359 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Sync DNS records from ops/domains.yaml to Cloudflare
|
||||||
|
|
||||||
|
This script automates the migration and synchronization of DNS records
|
||||||
|
from the domain configuration file to Cloudflare. It handles:
|
||||||
|
- Creating new DNS records
|
||||||
|
- Updating existing DNS records
|
||||||
|
- Detecting and reporting configuration drift
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
export CF_API_TOKEN="your-cloudflare-api-token"
|
||||||
|
export CF_ZONE_ID="your-zone-id" # For blackroad.systems
|
||||||
|
python scripts/cloudflare/sync_dns.py
|
||||||
|
|
||||||
|
Or with command-line arguments:
|
||||||
|
python scripts/cloudflare/sync_dns.py --zone-id <zone_id> --token <token>
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
pip install requests pyyaml colorama
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
import yaml
|
||||||
|
import requests
|
||||||
|
from typing import Dict, List, Optional
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
try:
|
||||||
|
from colorama import init, Fore, Style
|
||||||
|
init()
|
||||||
|
HAS_COLOR = True
|
||||||
|
except ImportError:
|
||||||
|
HAS_COLOR = False
|
||||||
|
# Fallback to no colors
|
||||||
|
class Fore:
|
||||||
|
GREEN = RED = YELLOW = CYAN = RESET = ""
|
||||||
|
class Style:
|
||||||
|
BRIGHT = RESET_ALL = ""
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
CF_API_BASE = "https://api.cloudflare.com/client/v4"
|
||||||
|
DOMAINS_FILE = "ops/domains.yaml"
|
||||||
|
|
||||||
|
|
||||||
|
def print_status(message: str, status: str = "info"):
|
||||||
|
"""Print colored status messages"""
|
||||||
|
if status == "success":
|
||||||
|
prefix = f"{Fore.GREEN}✓{Fore.RESET}"
|
||||||
|
elif status == "error":
|
||||||
|
prefix = f"{Fore.RED}✗{Fore.RESET}"
|
||||||
|
elif status == "warning":
|
||||||
|
prefix = f"{Fore.YELLOW}⚠{Fore.RESET}"
|
||||||
|
else:
|
||||||
|
prefix = f"{Fore.CYAN}ℹ{Fore.RESET}"
|
||||||
|
|
||||||
|
print(f"{prefix} {message}")
|
||||||
|
|
||||||
|
|
||||||
|
def get_api_headers(api_token: str) -> Dict[str, str]:
|
||||||
|
"""Get headers for Cloudflare API requests"""
|
||||||
|
return {
|
||||||
|
"Authorization": f"Bearer {api_token}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def load_domains() -> Dict:
|
||||||
|
"""Load domain configuration from ops/domains.yaml"""
|
||||||
|
try:
|
||||||
|
with open(DOMAINS_FILE) as f:
|
||||||
|
return yaml.safe_load(f)
|
||||||
|
except FileNotFoundError:
|
||||||
|
print_status(f"Error: {DOMAINS_FILE} not found", "error")
|
||||||
|
sys.exit(1)
|
||||||
|
except yaml.YAMLError as e:
|
||||||
|
print_status(f"Error parsing {DOMAINS_FILE}: {e}", "error")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_existing_records(zone_id: str, api_token: str) -> List[Dict]:
|
||||||
|
"""Fetch all DNS records for a zone"""
|
||||||
|
url = f"{CF_API_BASE}/zones/{zone_id}/dns_records"
|
||||||
|
headers = get_api_headers(api_token)
|
||||||
|
|
||||||
|
all_records = []
|
||||||
|
page = 1
|
||||||
|
per_page = 100
|
||||||
|
|
||||||
|
while True:
|
||||||
|
params = {"page": page, "per_page": per_page}
|
||||||
|
response = requests.get(url, headers=headers, params=params)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
print_status(f"Error fetching DNS records: {response.text}", "error")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
if not data.get("success"):
|
||||||
|
print_status(f"API error: {data.get('errors')}", "error")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
records = data.get("result", [])
|
||||||
|
all_records.extend(records)
|
||||||
|
|
||||||
|
# Check if there are more pages
|
||||||
|
result_info = data.get("result_info", {})
|
||||||
|
if page * per_page >= result_info.get("total_count", 0):
|
||||||
|
break
|
||||||
|
|
||||||
|
page += 1
|
||||||
|
|
||||||
|
return all_records
|
||||||
|
|
||||||
|
|
||||||
|
def create_dns_record(zone_id: str, api_token: str, record: Dict) -> Dict:
|
||||||
|
"""Create a DNS record"""
|
||||||
|
url = f"{CF_API_BASE}/zones/{zone_id}/dns_records"
|
||||||
|
headers = get_api_headers(api_token)
|
||||||
|
|
||||||
|
response = requests.post(url, headers=headers, json=record)
|
||||||
|
|
||||||
|
if response.status_code not in [200, 201]:
|
||||||
|
print_status(f"Error creating DNS record: {response.text}", "error")
|
||||||
|
return None
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
if not data.get("success"):
|
||||||
|
print_status(f"API error: {data.get('errors')}", "error")
|
||||||
|
return None
|
||||||
|
|
||||||
|
return data.get("result")
|
||||||
|
|
||||||
|
|
||||||
|
def update_dns_record(zone_id: str, api_token: str, record_id: str, record: Dict) -> Dict:
|
||||||
|
"""Update a DNS record"""
|
||||||
|
url = f"{CF_API_BASE}/zones/{zone_id}/dns_records/{record_id}"
|
||||||
|
headers = get_api_headers(api_token)
|
||||||
|
|
||||||
|
response = requests.put(url, headers=headers, json=record)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
print_status(f"Error updating DNS record: {response.text}", "error")
|
||||||
|
return None
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
if not data.get("success"):
|
||||||
|
print_status(f"API error: {data.get('errors')}", "error")
|
||||||
|
return None
|
||||||
|
|
||||||
|
return data.get("result")
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_record_name(name: str, zone_name: str) -> str:
|
||||||
|
"""Normalize record name for comparison
|
||||||
|
|
||||||
|
Cloudflare returns full domain names (e.g., 'blackroad.systems' or 'www.blackroad.systems')
|
||||||
|
while config may use '@' for apex or just subdomain names.
|
||||||
|
"""
|
||||||
|
if name == "@":
|
||||||
|
return zone_name
|
||||||
|
elif not name.endswith(zone_name):
|
||||||
|
return f"{name}.{zone_name}"
|
||||||
|
return name
|
||||||
|
|
||||||
|
|
||||||
|
def records_match(config_record: Dict, cf_record: Dict, zone_name: str) -> bool:
|
||||||
|
"""Check if a config record matches a Cloudflare record"""
|
||||||
|
config_name = normalize_record_name(config_record.get("name", ""), zone_name)
|
||||||
|
cf_name = cf_record.get("name", "")
|
||||||
|
|
||||||
|
return (
|
||||||
|
config_record.get("type") == cf_record.get("type") and
|
||||||
|
config_name == cf_name and
|
||||||
|
config_record.get("content") == cf_record.get("content")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def sync_records(zone_id: str, api_token: str, zone_name: str, dry_run: bool = False):
|
||||||
|
"""Sync DNS records from domains.yaml to Cloudflare"""
|
||||||
|
print_status(f"Starting DNS sync for zone: {zone_name}")
|
||||||
|
print_status(f"Zone ID: {zone_id}")
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
print_status("DRY RUN MODE - No changes will be made", "warning")
|
||||||
|
|
||||||
|
# Load configuration
|
||||||
|
config = load_domains()
|
||||||
|
|
||||||
|
# Get existing records from Cloudflare
|
||||||
|
print_status("Fetching existing DNS records from Cloudflare...")
|
||||||
|
existing = get_existing_records(zone_id, api_token)
|
||||||
|
print_status(f"Found {len(existing)} existing DNS records")
|
||||||
|
|
||||||
|
# Build index of existing records
|
||||||
|
existing_index = {}
|
||||||
|
for record in existing:
|
||||||
|
key = f"{record['type']}:{record['name']}"
|
||||||
|
existing_index[key] = record
|
||||||
|
|
||||||
|
# Process domains from config
|
||||||
|
created = 0
|
||||||
|
updated = 0
|
||||||
|
skipped = 0
|
||||||
|
errors = 0
|
||||||
|
|
||||||
|
for domain in config.get("domains", []):
|
||||||
|
# Only process domains configured for Cloudflare DNS mode
|
||||||
|
if domain.get("provider") != "cloudflare" or domain.get("mode") != "dns":
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Skip if no record config
|
||||||
|
if "record" not in domain:
|
||||||
|
print_status(f"Skipping {domain.get('name')}: No record configuration", "warning")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Extract domain name (handle both root and subdomain)
|
||||||
|
domain_name = domain.get("name", "")
|
||||||
|
|
||||||
|
# Build record data
|
||||||
|
record_config = domain["record"]
|
||||||
|
record_type = record_config.get("type", "CNAME")
|
||||||
|
record_value = record_config.get("value", "")
|
||||||
|
|
||||||
|
# Determine record name for Cloudflare
|
||||||
|
# For root domains matching zone name, use "@"
|
||||||
|
if domain_name == zone_name:
|
||||||
|
record_name = "@"
|
||||||
|
else:
|
||||||
|
record_name = domain_name
|
||||||
|
|
||||||
|
record_data = {
|
||||||
|
"type": record_type,
|
||||||
|
"name": record_name,
|
||||||
|
"content": record_value,
|
||||||
|
"ttl": record_config.get("ttl", 1), # 1 = Auto
|
||||||
|
"proxied": record_config.get("proxied", True)
|
||||||
|
}
|
||||||
|
|
||||||
|
# For MX records, add priority
|
||||||
|
if record_type == "MX":
|
||||||
|
record_data["priority"] = record_config.get("priority", 10)
|
||||||
|
|
||||||
|
# Build key for lookup
|
||||||
|
full_name = normalize_record_name(record_name, zone_name)
|
||||||
|
key = f"{record_type}:{full_name}"
|
||||||
|
|
||||||
|
# Check if record exists
|
||||||
|
if key in existing_index:
|
||||||
|
existing_record = existing_index[key]
|
||||||
|
|
||||||
|
# Check if update is needed
|
||||||
|
needs_update = (
|
||||||
|
existing_record.get("content") != record_value or
|
||||||
|
existing_record.get("proxied") != record_data.get("proxied")
|
||||||
|
)
|
||||||
|
|
||||||
|
if needs_update:
|
||||||
|
print_status(f"Updating: {key} -> {record_value}", "warning")
|
||||||
|
if not dry_run:
|
||||||
|
result = update_dns_record(zone_id, api_token, existing_record["id"], record_data)
|
||||||
|
if result:
|
||||||
|
updated += 1
|
||||||
|
print_status(f" Updated successfully", "success")
|
||||||
|
else:
|
||||||
|
errors += 1
|
||||||
|
else:
|
||||||
|
print_status(f" [DRY RUN] Would update", "info")
|
||||||
|
updated += 1
|
||||||
|
else:
|
||||||
|
print_status(f"Unchanged: {key}", "info")
|
||||||
|
skipped += 1
|
||||||
|
else:
|
||||||
|
# Create new record
|
||||||
|
print_status(f"Creating: {key} -> {record_value}", "warning")
|
||||||
|
if not dry_run:
|
||||||
|
result = create_dns_record(zone_id, api_token, record_data)
|
||||||
|
if result:
|
||||||
|
created += 1
|
||||||
|
print_status(f" Created successfully", "success")
|
||||||
|
else:
|
||||||
|
errors += 1
|
||||||
|
else:
|
||||||
|
print_status(f" [DRY RUN] Would create", "info")
|
||||||
|
created += 1
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
print("\n" + "="*60)
|
||||||
|
print_status("DNS Sync Complete!", "success")
|
||||||
|
print("="*60)
|
||||||
|
print(f" {Fore.GREEN}Created:{Fore.RESET} {created}")
|
||||||
|
print(f" {Fore.YELLOW}Updated:{Fore.RESET} {updated}")
|
||||||
|
print(f" {Fore.CYAN}Unchanged:{Fore.RESET} {skipped}")
|
||||||
|
print(f" {Fore.RED}Errors:{Fore.RESET} {errors}")
|
||||||
|
print("="*60)
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
print_status("This was a DRY RUN - no actual changes were made", "warning")
|
||||||
|
print_status("Run without --dry-run to apply changes", "info")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point"""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Sync DNS records from ops/domains.yaml to Cloudflare"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--token",
|
||||||
|
help="Cloudflare API token (or set CF_API_TOKEN env var)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--zone-id",
|
||||||
|
help="Cloudflare zone ID (or set CF_ZONE_ID env var)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--zone-name",
|
||||||
|
default="blackroad.systems",
|
||||||
|
help="Zone name (default: blackroad.systems)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--dry-run",
|
||||||
|
action="store_true",
|
||||||
|
help="Show what would be changed without making actual changes"
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Get credentials
|
||||||
|
api_token = args.token or os.getenv("CF_API_TOKEN")
|
||||||
|
zone_id = args.zone_id or os.getenv("CF_ZONE_ID")
|
||||||
|
|
||||||
|
if not api_token:
|
||||||
|
print_status("Error: CF_API_TOKEN environment variable or --token argument required", "error")
|
||||||
|
print_status("Get your token at: https://dash.cloudflare.com/profile/api-tokens", "info")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if not zone_id:
|
||||||
|
print_status("Error: CF_ZONE_ID environment variable or --zone-id argument required", "error")
|
||||||
|
print_status("Find your zone ID in the Cloudflare dashboard", "info")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Run sync
|
||||||
|
try:
|
||||||
|
sync_records(zone_id, api_token, args.zone_name, dry_run=args.dry_run)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n")
|
||||||
|
print_status("Interrupted by user", "warning")
|
||||||
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
print_status(f"Unexpected error: {e}", "error")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
313
scripts/cloudflare/validate_dns.py
Executable file
313
scripts/cloudflare/validate_dns.py
Executable file
@@ -0,0 +1,313 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Validate DNS configuration and check propagation status
|
||||||
|
|
||||||
|
This script helps verify that DNS records have been properly configured
|
||||||
|
and propagated across the internet. It performs:
|
||||||
|
- DNS resolution checks
|
||||||
|
- SSL certificate validation
|
||||||
|
- HTTP/HTTPS accessibility tests
|
||||||
|
- Redirect verification
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python scripts/cloudflare/validate_dns.py
|
||||||
|
python scripts/cloudflare/validate_dns.py --domain blackroad.systems
|
||||||
|
python scripts/cloudflare/validate_dns.py --all # Check all domains
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
pip install requests dnspython colorama
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import socket
|
||||||
|
import ssl
|
||||||
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import List, Dict, Optional
|
||||||
|
import requests
|
||||||
|
|
||||||
|
try:
|
||||||
|
import dns.resolver
|
||||||
|
HAS_DNS = True
|
||||||
|
except ImportError:
|
||||||
|
HAS_DNS = False
|
||||||
|
print("Warning: dnspython not installed. Install with: pip install dnspython")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from colorama import init, Fore, Style
|
||||||
|
init()
|
||||||
|
except ImportError:
|
||||||
|
class Fore:
|
||||||
|
GREEN = RED = YELLOW = CYAN = RESET = ""
|
||||||
|
class Style:
|
||||||
|
BRIGHT = RESET_ALL = ""
|
||||||
|
|
||||||
|
|
||||||
|
def print_status(message: str, status: str = "info"):
|
||||||
|
"""Print colored status messages"""
|
||||||
|
if status == "success":
|
||||||
|
prefix = f"{Fore.GREEN}✓{Fore.RESET}"
|
||||||
|
elif status == "error":
|
||||||
|
prefix = f"{Fore.RED}✗{Fore.RESET}"
|
||||||
|
elif status == "warning":
|
||||||
|
prefix = f"{Fore.YELLOW}⚠{Fore.RESET}"
|
||||||
|
else:
|
||||||
|
prefix = f"{Fore.CYAN}ℹ{Fore.RESET}"
|
||||||
|
|
||||||
|
print(f"{prefix} {message}")
|
||||||
|
|
||||||
|
|
||||||
|
def check_dns_resolution(domain: str) -> Dict:
|
||||||
|
"""Check DNS resolution for a domain"""
|
||||||
|
result = {
|
||||||
|
"domain": domain,
|
||||||
|
"resolved": False,
|
||||||
|
"ip_addresses": [],
|
||||||
|
"cname": None,
|
||||||
|
"error": None
|
||||||
|
}
|
||||||
|
|
||||||
|
if not HAS_DNS:
|
||||||
|
result["error"] = "dnspython not installed"
|
||||||
|
return result
|
||||||
|
|
||||||
|
try:
|
||||||
|
resolver = dns.resolver.Resolver()
|
||||||
|
resolver.timeout = 5
|
||||||
|
resolver.lifetime = 5
|
||||||
|
|
||||||
|
# Try CNAME first
|
||||||
|
try:
|
||||||
|
cname_answers = resolver.resolve(domain, 'CNAME')
|
||||||
|
if cname_answers:
|
||||||
|
result["cname"] = str(cname_answers[0].target).rstrip('.')
|
||||||
|
print_status(f" CNAME: {result['cname']}", "info")
|
||||||
|
except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Try A record
|
||||||
|
try:
|
||||||
|
answers = resolver.resolve(domain, 'A')
|
||||||
|
result["ip_addresses"] = [str(rdata) for rdata in answers]
|
||||||
|
result["resolved"] = True
|
||||||
|
for ip in result["ip_addresses"]:
|
||||||
|
print_status(f" A: {ip}", "info")
|
||||||
|
except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN) as e:
|
||||||
|
result["error"] = str(e)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
result["error"] = str(e)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def check_ssl_certificate(domain: str) -> Dict:
|
||||||
|
"""Check SSL certificate for a domain"""
|
||||||
|
result = {
|
||||||
|
"domain": domain,
|
||||||
|
"valid": False,
|
||||||
|
"issuer": None,
|
||||||
|
"expires": None,
|
||||||
|
"days_remaining": None,
|
||||||
|
"error": None
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
context = ssl.create_default_context()
|
||||||
|
with socket.create_connection((domain, 443), timeout=10) as sock:
|
||||||
|
with context.wrap_socket(sock, server_hostname=domain) as ssock:
|
||||||
|
cert = ssock.getpeercert()
|
||||||
|
|
||||||
|
result["issuer"] = dict(x[0] for x in cert['issuer'])
|
||||||
|
result["expires"] = cert['notAfter']
|
||||||
|
|
||||||
|
# Parse expiry date
|
||||||
|
expire_date = datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z')
|
||||||
|
days_remaining = (expire_date - datetime.now()).days
|
||||||
|
result["days_remaining"] = days_remaining
|
||||||
|
result["valid"] = days_remaining > 0
|
||||||
|
|
||||||
|
print_status(f" Issuer: {result['issuer'].get('organizationName', 'Unknown')}", "info")
|
||||||
|
print_status(f" Expires: {result['expires']} ({days_remaining} days)",
|
||||||
|
"success" if days_remaining > 30 else "warning")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
result["error"] = str(e)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def check_http_accessibility(domain: str, check_redirects: bool = True) -> Dict:
|
||||||
|
"""Check HTTP/HTTPS accessibility and redirects"""
|
||||||
|
result = {
|
||||||
|
"domain": domain,
|
||||||
|
"http_accessible": False,
|
||||||
|
"https_accessible": False,
|
||||||
|
"redirects_to_https": False,
|
||||||
|
"www_redirects": False,
|
||||||
|
"status_code": None,
|
||||||
|
"error": None
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Check HTTP -> HTTPS redirect
|
||||||
|
http_response = requests.get(f"http://{domain}", allow_redirects=True, timeout=10)
|
||||||
|
result["http_accessible"] = True
|
||||||
|
result["redirects_to_https"] = http_response.url.startswith("https://")
|
||||||
|
|
||||||
|
if result["redirects_to_https"]:
|
||||||
|
print_status(f" HTTP → HTTPS redirect: ✓", "success")
|
||||||
|
else:
|
||||||
|
print_status(f" HTTP → HTTPS redirect: ✗", "warning")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
result["error"] = f"HTTP check failed: {e}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Check HTTPS accessibility
|
||||||
|
https_response = requests.get(f"https://{domain}", timeout=10)
|
||||||
|
result["https_accessible"] = https_response.status_code == 200
|
||||||
|
result["status_code"] = https_response.status_code
|
||||||
|
|
||||||
|
if result["https_accessible"]:
|
||||||
|
print_status(f" HTTPS accessible: ✓ (Status: {https_response.status_code})", "success")
|
||||||
|
else:
|
||||||
|
print_status(f" HTTPS status: {https_response.status_code}", "warning")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
if not result["error"]:
|
||||||
|
result["error"] = f"HTTPS check failed: {e}"
|
||||||
|
|
||||||
|
# Check www redirect if requested
|
||||||
|
if check_redirects and not domain.startswith("www."):
|
||||||
|
try:
|
||||||
|
www_response = requests.get(f"https://www.{domain}", allow_redirects=True, timeout=10)
|
||||||
|
result["www_redirects"] = www_response.url == f"https://{domain}/" or www_response.url == f"https://{domain}"
|
||||||
|
|
||||||
|
if result["www_redirects"]:
|
||||||
|
print_status(f" www → apex redirect: ✓", "success")
|
||||||
|
else:
|
||||||
|
print_status(f" www redirect: ✗ (goes to {www_response.url})", "warning")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print_status(f" www redirect check failed: {e}", "info")
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def validate_domain(domain: str, full_check: bool = True) -> bool:
|
||||||
|
"""Validate a single domain"""
|
||||||
|
print(f"\n{'='*60}")
|
||||||
|
print(f"{Style.BRIGHT}Validating: {domain}{Style.RESET_ALL}")
|
||||||
|
print(f"{'='*60}")
|
||||||
|
|
||||||
|
all_passed = True
|
||||||
|
|
||||||
|
# DNS Resolution
|
||||||
|
print(f"\n{Fore.CYAN}[1/3] DNS Resolution{Fore.RESET}")
|
||||||
|
dns_result = check_dns_resolution(domain)
|
||||||
|
if dns_result["resolved"]:
|
||||||
|
print_status(f"DNS resolved successfully", "success")
|
||||||
|
else:
|
||||||
|
print_status(f"DNS resolution failed: {dns_result.get('error', 'Unknown error')}", "error")
|
||||||
|
all_passed = False
|
||||||
|
|
||||||
|
if not full_check:
|
||||||
|
return all_passed
|
||||||
|
|
||||||
|
# SSL Certificate
|
||||||
|
print(f"\n{Fore.CYAN}[2/3] SSL Certificate{Fore.RESET}")
|
||||||
|
ssl_result = check_ssl_certificate(domain)
|
||||||
|
if ssl_result["valid"]:
|
||||||
|
print_status(f"SSL certificate is valid", "success")
|
||||||
|
else:
|
||||||
|
print_status(f"SSL certificate check failed: {ssl_result.get('error', 'Unknown error')}", "error")
|
||||||
|
all_passed = False
|
||||||
|
|
||||||
|
# HTTP Accessibility
|
||||||
|
print(f"\n{Fore.CYAN}[3/3] HTTP Accessibility{Fore.RESET}")
|
||||||
|
http_result = check_http_accessibility(domain)
|
||||||
|
if http_result["https_accessible"]:
|
||||||
|
print_status(f"Site is accessible via HTTPS", "success")
|
||||||
|
else:
|
||||||
|
print_status(f"Site accessibility check failed: {http_result.get('error', 'Unknown error')}", "error")
|
||||||
|
all_passed = False
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
print(f"\n{'='*60}")
|
||||||
|
if all_passed:
|
||||||
|
print_status(f"{domain}: All checks passed! ✓", "success")
|
||||||
|
else:
|
||||||
|
print_status(f"{domain}: Some checks failed", "warning")
|
||||||
|
print(f"{'='*60}")
|
||||||
|
|
||||||
|
return all_passed
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point"""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Validate DNS configuration and check propagation status"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--domain",
|
||||||
|
help="Domain to validate (default: blackroad.systems)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--all",
|
||||||
|
action="store_true",
|
||||||
|
help="Check all BlackRoad domains"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--dns-only",
|
||||||
|
action="store_true",
|
||||||
|
help="Only check DNS resolution (skip SSL and HTTP checks)"
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# List of BlackRoad domains
|
||||||
|
all_domains = [
|
||||||
|
"blackroad.systems",
|
||||||
|
"blackroad.ai",
|
||||||
|
"blackroad.network",
|
||||||
|
"blackroad.me",
|
||||||
|
"lucidia.earth",
|
||||||
|
"aliceqi.com",
|
||||||
|
"blackroadqi.com",
|
||||||
|
"roadwallet.com",
|
||||||
|
"aliceos.io",
|
||||||
|
"blackroadquantum.com"
|
||||||
|
]
|
||||||
|
|
||||||
|
if args.all:
|
||||||
|
domains = all_domains
|
||||||
|
elif args.domain:
|
||||||
|
domains = [args.domain]
|
||||||
|
else:
|
||||||
|
domains = ["blackroad.systems"]
|
||||||
|
|
||||||
|
full_check = not args.dns_only
|
||||||
|
all_passed = True
|
||||||
|
|
||||||
|
for domain in domains:
|
||||||
|
passed = validate_domain(domain, full_check=full_check)
|
||||||
|
if not passed:
|
||||||
|
all_passed = False
|
||||||
|
|
||||||
|
# Final summary
|
||||||
|
print(f"\n{'='*60}")
|
||||||
|
print(f"{Style.BRIGHT}VALIDATION SUMMARY{Style.RESET_ALL}")
|
||||||
|
print(f"{'='*60}")
|
||||||
|
print(f"Domains checked: {len(domains)}")
|
||||||
|
|
||||||
|
if all_passed:
|
||||||
|
print_status("All validations passed!", "success")
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
print_status("Some validations failed", "warning")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user