diff --git a/.github/workflows/sync-cloudflare-dns.yml b/.github/workflows/sync-cloudflare-dns.yml new file mode 100644 index 0000000..21d5f4b --- /dev/null +++ b/.github/workflows/sync-cloudflare-dns.yml @@ -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!' + }) diff --git a/backend/.env.example b/backend/.env.example index 4b9b6c9..5fd3168 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -57,7 +57,12 @@ RAILWAY_ENVIRONMENT_ID=00000000-0000-0000-0000-000000000000 RAILWAY_DOMAIN=your-service.up.railway.app SLACK_WEBHOOK_URL=https://hooks.slack.com/services/XXX/YYY/ZZZ DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/XXX/YYY + +# Cloudflare DNS and CDN 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 DIGITAL_OCEAN_API_KEY=your-digital-ocean-api-key diff --git a/docs/CLOUDFLARE_MIGRATION_GUIDE.md b/docs/CLOUDFLARE_MIGRATION_GUIDE.md new file mode 100644 index 0000000..780f4ab --- /dev/null +++ b/docs/CLOUDFLARE_MIGRATION_GUIDE.md @@ -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 diff --git a/scripts/cloudflare/README.md b/scripts/cloudflare/README.md new file mode 100644 index 0000000..1a0529b --- /dev/null +++ b/scripts/cloudflare/README.md @@ -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 +``` + +### 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 diff --git a/scripts/cloudflare/requirements.txt b/scripts/cloudflare/requirements.txt new file mode 100644 index 0000000..abf30e6 --- /dev/null +++ b/scripts/cloudflare/requirements.txt @@ -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 diff --git a/scripts/cloudflare/sync_dns.py b/scripts/cloudflare/sync_dns.py new file mode 100755 index 0000000..c3be3f0 --- /dev/null +++ b/scripts/cloudflare/sync_dns.py @@ -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 --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() diff --git a/scripts/cloudflare/validate_dns.py b/scripts/cloudflare/validate_dns.py new file mode 100755 index 0000000..1cad26b --- /dev/null +++ b/scripts/cloudflare/validate_dns.py @@ -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()