mirror of
https://github.com/blackboxprogramming/BlackRoad-Operating-System.git
synced 2026-03-18 01:34:00 -05:00
Merge branch 'main' into claude/celebrate-cece-01U7rFKSt1xaRDcyWAqd1ijj
This commit is contained in:
255
.github/dependabot.yml
vendored
Normal file
255
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
# Dependabot Configuration
|
||||||
|
# ========================
|
||||||
|
#
|
||||||
|
# Dependabot automatically creates pull requests to update dependencies.
|
||||||
|
# This keeps your project secure and up-to-date.
|
||||||
|
#
|
||||||
|
# Documentation: https://docs.github.com/en/code-security/dependabot
|
||||||
|
#
|
||||||
|
# What Dependabot does:
|
||||||
|
# --------------------
|
||||||
|
# - Checks for outdated dependencies daily/weekly/monthly
|
||||||
|
# - Creates PRs to update them
|
||||||
|
# - Groups related updates together
|
||||||
|
# - Respects semantic versioning
|
||||||
|
# - Works with GitHub Actions to run tests
|
||||||
|
#
|
||||||
|
# How to use:
|
||||||
|
# ----------
|
||||||
|
# This file is already in the correct location (.github/dependabot.yml)
|
||||||
|
# Just commit it and Dependabot will start working automatically!
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
updates:
|
||||||
|
# ========================================
|
||||||
|
# Backend Python Dependencies
|
||||||
|
# ========================================
|
||||||
|
- package-ecosystem: "pip"
|
||||||
|
directory: "/backend"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
day: "monday"
|
||||||
|
time: "06:00"
|
||||||
|
open-pull-requests-limit: 5
|
||||||
|
reviewers:
|
||||||
|
- "alexa-amundson"
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
|
- "backend"
|
||||||
|
- "python"
|
||||||
|
commit-message:
|
||||||
|
prefix: "chore(deps)"
|
||||||
|
include: "scope"
|
||||||
|
# Group minor and patch updates together
|
||||||
|
groups:
|
||||||
|
fastapi-stack:
|
||||||
|
patterns:
|
||||||
|
- "fastapi*"
|
||||||
|
- "uvicorn*"
|
||||||
|
- "pydantic*"
|
||||||
|
- "starlette*"
|
||||||
|
database-stack:
|
||||||
|
patterns:
|
||||||
|
- "sqlalchemy*"
|
||||||
|
- "alembic*"
|
||||||
|
- "psycopg*"
|
||||||
|
- "asyncpg*"
|
||||||
|
testing-stack:
|
||||||
|
patterns:
|
||||||
|
- "pytest*"
|
||||||
|
- "coverage*"
|
||||||
|
- "httpx*"
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# Frontend NPM Dependencies (if exists)
|
||||||
|
# ========================================
|
||||||
|
# Uncomment if you have a package.json for frontend
|
||||||
|
# - package-ecosystem: "npm"
|
||||||
|
# directory: "/frontend"
|
||||||
|
# schedule:
|
||||||
|
# interval: "weekly"
|
||||||
|
# day: "monday"
|
||||||
|
# time: "06:00"
|
||||||
|
# open-pull-requests-limit: 5
|
||||||
|
# reviewers:
|
||||||
|
# - "alexa-amundson"
|
||||||
|
# labels:
|
||||||
|
# - "dependencies"
|
||||||
|
# - "frontend"
|
||||||
|
# - "javascript"
|
||||||
|
# commit-message:
|
||||||
|
# prefix: "chore(deps)"
|
||||||
|
# groups:
|
||||||
|
# dev-dependencies:
|
||||||
|
# dependency-type: "development"
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# Python SDK Dependencies
|
||||||
|
# ========================================
|
||||||
|
- package-ecosystem: "pip"
|
||||||
|
directory: "/sdk/python"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
day: "monday"
|
||||||
|
time: "06:00"
|
||||||
|
open-pull-requests-limit: 3
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
|
- "sdk"
|
||||||
|
- "python"
|
||||||
|
commit-message:
|
||||||
|
prefix: "chore(sdk)"
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# TypeScript SDK Dependencies
|
||||||
|
# ========================================
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/sdk/typescript"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
day: "monday"
|
||||||
|
time: "06:00"
|
||||||
|
open-pull-requests-limit: 3
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
|
- "sdk"
|
||||||
|
- "typescript"
|
||||||
|
commit-message:
|
||||||
|
prefix: "chore(sdk)"
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# GitHub Actions Workflows
|
||||||
|
# ========================================
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
day: "monday"
|
||||||
|
time: "06:00"
|
||||||
|
open-pull-requests-limit: 3
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
|
- "github-actions"
|
||||||
|
- "ci-cd"
|
||||||
|
commit-message:
|
||||||
|
prefix: "chore(ci)"
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# Docker (if using Dockerfile)
|
||||||
|
# ========================================
|
||||||
|
- package-ecosystem: "docker"
|
||||||
|
directory: "/backend"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
day: "monday"
|
||||||
|
time: "06:00"
|
||||||
|
open-pull-requests-limit: 2
|
||||||
|
labels:
|
||||||
|
- "dependencies"
|
||||||
|
- "docker"
|
||||||
|
commit-message:
|
||||||
|
prefix: "chore(docker)"
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# Configuration Options Explained
|
||||||
|
# ========================================
|
||||||
|
#
|
||||||
|
# package-ecosystem:
|
||||||
|
# - pip: Python (requirements.txt, setup.py)
|
||||||
|
# - npm: JavaScript/TypeScript (package.json)
|
||||||
|
# - github-actions: GitHub Actions workflows
|
||||||
|
# - docker: Dockerfiles
|
||||||
|
# - bundler: Ruby (Gemfile)
|
||||||
|
# - composer: PHP (composer.json)
|
||||||
|
# - cargo: Rust (Cargo.toml)
|
||||||
|
#
|
||||||
|
# directory:
|
||||||
|
# - Path where the manifest file is located
|
||||||
|
# - Use "/" for root, "/backend" for subdirectory
|
||||||
|
#
|
||||||
|
# schedule.interval:
|
||||||
|
# - daily: Check every day
|
||||||
|
# - weekly: Check once a week
|
||||||
|
# - monthly: Check once a month
|
||||||
|
#
|
||||||
|
# open-pull-requests-limit:
|
||||||
|
# - Max number of open dependency PRs at once
|
||||||
|
# - Prevents PR spam
|
||||||
|
# - Recommended: 3-10
|
||||||
|
#
|
||||||
|
# reviewers:
|
||||||
|
# - GitHub usernames to request review from
|
||||||
|
# - Helps ensure updates are reviewed
|
||||||
|
#
|
||||||
|
# labels:
|
||||||
|
# - Labels to add to PRs
|
||||||
|
# - Helps organize and filter
|
||||||
|
#
|
||||||
|
# groups:
|
||||||
|
# - Group related dependencies into single PR
|
||||||
|
# - Reduces PR noise
|
||||||
|
# - Example: Update all pytest-related packages together
|
||||||
|
#
|
||||||
|
# commit-message.prefix:
|
||||||
|
# - Prefix for commit messages
|
||||||
|
# - Common: "chore(deps)", "build", "deps"
|
||||||
|
#
|
||||||
|
# ========================================
|
||||||
|
# Best Practices
|
||||||
|
# ========================================
|
||||||
|
#
|
||||||
|
# 1. Review PRs before merging:
|
||||||
|
# - Check changelogs
|
||||||
|
# - Run tests
|
||||||
|
# - Look for breaking changes
|
||||||
|
#
|
||||||
|
# 2. Use groups to reduce PR noise:
|
||||||
|
# - Group related packages
|
||||||
|
# - Group by type (dev vs prod)
|
||||||
|
#
|
||||||
|
# 3. Set appropriate schedule:
|
||||||
|
# - Weekly for active projects
|
||||||
|
# - Monthly for stable projects
|
||||||
|
# - Daily for security-critical projects
|
||||||
|
#
|
||||||
|
# 4. Limit open PRs:
|
||||||
|
# - Prevents backlog of unreviewed PRs
|
||||||
|
# - 5-10 is usually good
|
||||||
|
#
|
||||||
|
# 5. Auto-merge patch updates (optional):
|
||||||
|
# - Use GitHub auto-merge for patch versions
|
||||||
|
# - Requires passing CI tests
|
||||||
|
#
|
||||||
|
# ========================================
|
||||||
|
# Monitoring
|
||||||
|
# ========================================
|
||||||
|
#
|
||||||
|
# View Dependabot activity:
|
||||||
|
# - Repository → Insights → Dependency graph → Dependabot
|
||||||
|
# - Repository → Pull requests (filter by label: "dependencies")
|
||||||
|
#
|
||||||
|
# Configure alerts:
|
||||||
|
# - Repository → Settings → Security & analysis
|
||||||
|
# - Enable "Dependabot alerts"
|
||||||
|
# - Enable "Dependabot security updates"
|
||||||
|
#
|
||||||
|
# ========================================
|
||||||
|
# Troubleshooting
|
||||||
|
# ========================================
|
||||||
|
#
|
||||||
|
# Dependabot not creating PRs?
|
||||||
|
# - Check .github/dependabot.yml is valid YAML
|
||||||
|
# - Verify manifest files exist (requirements.txt, package.json)
|
||||||
|
# - Check repository settings → Security & analysis
|
||||||
|
# - Look for errors in Settings → Dependabot
|
||||||
|
#
|
||||||
|
# Too many PRs?
|
||||||
|
# - Reduce open-pull-requests-limit
|
||||||
|
# - Change schedule to monthly
|
||||||
|
# - Use groups to combine updates
|
||||||
|
#
|
||||||
|
# PRs failing tests?
|
||||||
|
# - Fix breaking changes before merging
|
||||||
|
# - Pin problematic dependencies
|
||||||
|
# - Use version ranges in manifest files
|
||||||
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!'
|
||||||
|
})
|
||||||
134
.github/workflows/templates/codeql-analysis-template.yml
vendored
Normal file
134
.github/workflows/templates/codeql-analysis-template.yml
vendored
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
# CodeQL Security Analysis Workflow Template
|
||||||
|
# ==========================================
|
||||||
|
#
|
||||||
|
# This template sets up CodeQL code scanning for security vulnerabilities.
|
||||||
|
#
|
||||||
|
# How to use:
|
||||||
|
# -----------
|
||||||
|
# 1. Copy this file to .github/workflows/codeql-analysis.yml in your repo
|
||||||
|
# 2. Update the languages array based on your repo (python, javascript, typescript, etc.)
|
||||||
|
# 3. Customize paths to analyze if needed
|
||||||
|
# 4. Commit and push - CodeQL will run automatically
|
||||||
|
#
|
||||||
|
# What is CodeQL?
|
||||||
|
# --------------
|
||||||
|
# CodeQL is GitHub's semantic code analysis engine that finds security vulnerabilities
|
||||||
|
# and coding errors. It's free for public repos and GitHub Enterprise.
|
||||||
|
#
|
||||||
|
# Supported languages:
|
||||||
|
# -------------------
|
||||||
|
# - python
|
||||||
|
# - javascript (includes TypeScript)
|
||||||
|
# - go
|
||||||
|
# - java
|
||||||
|
# - csharp
|
||||||
|
# - cpp
|
||||||
|
# - ruby
|
||||||
|
# - swift
|
||||||
|
|
||||||
|
name: CodeQL Security Analysis
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- develop
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- develop
|
||||||
|
schedule:
|
||||||
|
# Run CodeQL analysis every Monday at 00:00 UTC
|
||||||
|
- cron: '0 0 * * 1'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
# Limit concurrent runs
|
||||||
|
concurrency:
|
||||||
|
group: codeql-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: CodeQL Analysis
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 30
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
# Required for CodeQL to upload results
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
# Update this array based on your repository's languages
|
||||||
|
# For BlackRoad OS backend: ['python', 'javascript']
|
||||||
|
# For frontend only: ['javascript']
|
||||||
|
language: ['python', 'javascript']
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# ========================================
|
||||||
|
# 1. Checkout code
|
||||||
|
# ========================================
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# 2. Initialize CodeQL
|
||||||
|
# ========================================
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v3
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you want to analyze specific paths only:
|
||||||
|
# paths:
|
||||||
|
# - backend/
|
||||||
|
# - src/
|
||||||
|
# paths-ignore:
|
||||||
|
# - tests/
|
||||||
|
# - '**/*.test.js'
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# 3. Build code (if needed)
|
||||||
|
# ========================================
|
||||||
|
# For compiled languages (Java, C#, C++), add build steps here
|
||||||
|
# For interpreted languages (Python, JavaScript), auto-build works
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v3
|
||||||
|
|
||||||
|
# Alternative: Manual build steps for Python if needed
|
||||||
|
# - name: Build Python (manual)
|
||||||
|
# if: matrix.language == 'python'
|
||||||
|
# run: |
|
||||||
|
# python -m pip install --upgrade pip
|
||||||
|
# pip install -r backend/requirements.txt
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# 4. Perform CodeQL Analysis
|
||||||
|
# ========================================
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v3
|
||||||
|
with:
|
||||||
|
category: "/language:${{ matrix.language }}"
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# Workflow Summary
|
||||||
|
# ========================================
|
||||||
|
#
|
||||||
|
# This workflow:
|
||||||
|
# 1. Runs on push, PR, schedule (weekly), and manual dispatch
|
||||||
|
# 2. Analyzes code for security vulnerabilities using CodeQL
|
||||||
|
# 3. Uploads results to GitHub Security tab
|
||||||
|
# 4. Creates alerts for any issues found
|
||||||
|
#
|
||||||
|
# View results:
|
||||||
|
# - Go to your repository → Security tab → Code scanning alerts
|
||||||
|
#
|
||||||
|
# Customization:
|
||||||
|
# - Add more languages to matrix.language array
|
||||||
|
# - Filter paths to analyze specific directories
|
||||||
|
# - Adjust schedule frequency
|
||||||
|
# - Add custom queries for domain-specific security checks
|
||||||
418
.github/workflows/templates/comprehensive-ci-template.yml
vendored
Normal file
418
.github/workflows/templates/comprehensive-ci-template.yml
vendored
Normal file
@@ -0,0 +1,418 @@
|
|||||||
|
# Comprehensive CI Workflow Template
|
||||||
|
# ===================================
|
||||||
|
#
|
||||||
|
# This template provides a complete CI pipeline for BlackRoad repositories.
|
||||||
|
# It includes linting, type checking, testing, and build verification.
|
||||||
|
#
|
||||||
|
# How to use:
|
||||||
|
# -----------
|
||||||
|
# 1. Copy this file to .github/workflows/ci.yml in your repo
|
||||||
|
# 2. Customize the jobs based on your stack (Python/JS/both)
|
||||||
|
# 3. Update paths and commands as needed
|
||||||
|
# 4. Commit and push - CI will run on PRs and pushes
|
||||||
|
#
|
||||||
|
# What this workflow does:
|
||||||
|
# -----------------------
|
||||||
|
# - Lints code (Python: flake8/black, JS: eslint/prettier)
|
||||||
|
# - Runs type checking (Python: mypy, TS: tsc)
|
||||||
|
# - Runs test suite with coverage
|
||||||
|
# - Builds the application
|
||||||
|
# - Validates environment config
|
||||||
|
# - Reports results to PR
|
||||||
|
|
||||||
|
name: Continuous Integration
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- develop
|
||||||
|
- 'feature/**'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- develop
|
||||||
|
|
||||||
|
# Cancel previous runs if new push
|
||||||
|
concurrency:
|
||||||
|
group: ci-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
PYTHON_VERSION: '3.11'
|
||||||
|
NODE_VERSION: '20'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# ========================================
|
||||||
|
# Job 1: Python Linting
|
||||||
|
# ========================================
|
||||||
|
lint-python:
|
||||||
|
name: Lint Python Code
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
|
cache: 'pip'
|
||||||
|
cache-dependency-path: 'backend/requirements.txt'
|
||||||
|
|
||||||
|
- name: Install linting tools
|
||||||
|
run: |
|
||||||
|
pip install flake8 black isort mypy
|
||||||
|
|
||||||
|
- name: Run Black (code formatter check)
|
||||||
|
run: |
|
||||||
|
black --check backend/app
|
||||||
|
echo "✅ Black formatting check passed"
|
||||||
|
|
||||||
|
- name: Run isort (import sorting check)
|
||||||
|
run: |
|
||||||
|
isort --check-only backend/app
|
||||||
|
echo "✅ isort check passed"
|
||||||
|
|
||||||
|
- name: Run flake8 (linter)
|
||||||
|
run: |
|
||||||
|
flake8 backend/app --max-line-length=100 --ignore=E203,W503
|
||||||
|
echo "✅ flake8 linting passed"
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# Job 2: Python Type Checking
|
||||||
|
# ========================================
|
||||||
|
type-check-python:
|
||||||
|
name: Python Type Check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
|
cache: 'pip'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
pip install -r backend/requirements.txt
|
||||||
|
pip install mypy types-redis types-requests
|
||||||
|
|
||||||
|
- name: Run mypy
|
||||||
|
run: |
|
||||||
|
mypy backend/app --ignore-missing-imports
|
||||||
|
echo "✅ Type checking passed"
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# Job 3: Python Tests
|
||||||
|
# ========================================
|
||||||
|
test-python:
|
||||||
|
name: Python Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
|
||||||
|
# Test matrix for multiple Python versions (optional)
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: ['3.11'] # Add ['3.10', '3.11', '3.12'] for multi-version testing
|
||||||
|
|
||||||
|
services:
|
||||||
|
# PostgreSQL for tests
|
||||||
|
postgres:
|
||||||
|
image: postgres:15
|
||||||
|
env:
|
||||||
|
POSTGRES_USER: test
|
||||||
|
POSTGRES_PASSWORD: test
|
||||||
|
POSTGRES_DB: blackroad_test
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
|
||||||
|
# Redis for tests
|
||||||
|
redis:
|
||||||
|
image: redis:7
|
||||||
|
options: >-
|
||||||
|
--health-cmd "redis-cli ping"
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
ports:
|
||||||
|
- 6379:6379
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
cache: 'pip'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
pip install -r backend/requirements.txt
|
||||||
|
pip install pytest pytest-cov pytest-asyncio
|
||||||
|
|
||||||
|
- name: Set up test environment
|
||||||
|
env:
|
||||||
|
DATABASE_URL: postgresql://test:test@localhost:5432/blackroad_test
|
||||||
|
REDIS_URL: redis://localhost:6379/0
|
||||||
|
SECRET_KEY: test-secret-key-for-ci
|
||||||
|
ENVIRONMENT: test
|
||||||
|
run: |
|
||||||
|
echo "Test environment configured"
|
||||||
|
|
||||||
|
- name: Run tests with coverage
|
||||||
|
env:
|
||||||
|
DATABASE_URL: postgresql://test:test@localhost:5432/blackroad_test
|
||||||
|
REDIS_URL: redis://localhost:6379/0
|
||||||
|
SECRET_KEY: test-secret-key-for-ci
|
||||||
|
ENVIRONMENT: test
|
||||||
|
run: |
|
||||||
|
cd backend
|
||||||
|
pytest -v \
|
||||||
|
--cov=app \
|
||||||
|
--cov-report=term \
|
||||||
|
--cov-report=xml \
|
||||||
|
--cov-report=html \
|
||||||
|
--maxfail=3
|
||||||
|
|
||||||
|
- name: Upload coverage to Codecov (optional)
|
||||||
|
if: success()
|
||||||
|
uses: codecov/codecov-action@v3
|
||||||
|
with:
|
||||||
|
file: ./backend/coverage.xml
|
||||||
|
flags: backend
|
||||||
|
name: backend-coverage
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# Job 4: JavaScript/TypeScript Linting (if applicable)
|
||||||
|
# ========================================
|
||||||
|
lint-javascript:
|
||||||
|
name: Lint JavaScript/TypeScript
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
# Only run if package.json exists
|
||||||
|
if: hashFiles('package.json') != ''
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Run ESLint
|
||||||
|
run: npm run lint
|
||||||
|
|
||||||
|
- name: Run Prettier check
|
||||||
|
run: npm run format:check
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# Job 5: JavaScript/TypeScript Tests (if applicable)
|
||||||
|
# ========================================
|
||||||
|
test-javascript:
|
||||||
|
name: JavaScript/TypeScript Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
if: hashFiles('package.json') != ''
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: npm test -- --coverage
|
||||||
|
|
||||||
|
- name: Upload coverage
|
||||||
|
if: success()
|
||||||
|
uses: codecov/codecov-action@v3
|
||||||
|
with:
|
||||||
|
file: ./coverage/coverage-final.json
|
||||||
|
flags: frontend
|
||||||
|
name: frontend-coverage
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# Job 6: Build Verification
|
||||||
|
# ========================================
|
||||||
|
build:
|
||||||
|
name: Build Verification
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Python build (if applicable)
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
|
- name: Verify Python build
|
||||||
|
run: |
|
||||||
|
pip install build
|
||||||
|
cd backend
|
||||||
|
python -m build
|
||||||
|
echo "✅ Python package builds successfully"
|
||||||
|
|
||||||
|
# Node build (if applicable)
|
||||||
|
- name: Set up Node.js
|
||||||
|
if: hashFiles('package.json') != ''
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Build frontend (if applicable)
|
||||||
|
if: hashFiles('package.json') != ''
|
||||||
|
run: |
|
||||||
|
npm ci
|
||||||
|
npm run build
|
||||||
|
echo "✅ Frontend builds successfully"
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# Job 7: Environment Validation
|
||||||
|
# ========================================
|
||||||
|
validate-env:
|
||||||
|
name: Validate Environment Config
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Validate .env.example exists
|
||||||
|
run: |
|
||||||
|
if [ ! -f backend/.env.example ]; then
|
||||||
|
echo "❌ backend/.env.example not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✅ .env.example exists"
|
||||||
|
|
||||||
|
- name: Validate .env.example is not committed
|
||||||
|
run: |
|
||||||
|
if [ -f backend/.env ]; then
|
||||||
|
echo "❌ backend/.env should not be committed!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✅ .env is gitignored"
|
||||||
|
|
||||||
|
- name: Validate Railway config
|
||||||
|
run: |
|
||||||
|
if [ -f railway.toml ]; then
|
||||||
|
echo "✅ railway.toml exists"
|
||||||
|
else
|
||||||
|
echo "⚠️ railway.toml not found (not required but recommended)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# Job 8: Security Scan (optional)
|
||||||
|
# ========================================
|
||||||
|
security-scan:
|
||||||
|
name: Security Scan
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Run Bandit (Python security linter)
|
||||||
|
run: |
|
||||||
|
pip install bandit
|
||||||
|
bandit -r backend/app -ll
|
||||||
|
echo "✅ Security scan passed"
|
||||||
|
|
||||||
|
- name: Check for secrets in code
|
||||||
|
uses: trufflesecurity/trufflehog@main
|
||||||
|
with:
|
||||||
|
path: ./
|
||||||
|
base: ${{ github.event.repository.default_branch }}
|
||||||
|
head: HEAD
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# Job 9: Summary
|
||||||
|
# ========================================
|
||||||
|
ci-summary:
|
||||||
|
name: CI Summary
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- lint-python
|
||||||
|
- type-check-python
|
||||||
|
- test-python
|
||||||
|
- build
|
||||||
|
- validate-env
|
||||||
|
if: always()
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check all jobs passed
|
||||||
|
if: |
|
||||||
|
needs.lint-python.result == 'failure' ||
|
||||||
|
needs.type-check-python.result == 'failure' ||
|
||||||
|
needs.test-python.result == 'failure' ||
|
||||||
|
needs.build.result == 'failure' ||
|
||||||
|
needs.validate-env.result == 'failure'
|
||||||
|
run: |
|
||||||
|
echo "❌ CI failed - check job logs above"
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: All checks passed
|
||||||
|
run: |
|
||||||
|
echo "✅ All CI checks passed!"
|
||||||
|
echo "Code is ready to merge"
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# Workflow Summary
|
||||||
|
# ========================================
|
||||||
|
#
|
||||||
|
# This comprehensive CI workflow:
|
||||||
|
# 1. Lints Python code (black, isort, flake8)
|
||||||
|
# 2. Type checks Python (mypy)
|
||||||
|
# 3. Runs Python tests with coverage (pytest)
|
||||||
|
# 4. Lints JavaScript/TypeScript (eslint, prettier)
|
||||||
|
# 5. Runs JS/TS tests (jest)
|
||||||
|
# 6. Verifies build succeeds
|
||||||
|
# 7. Validates environment configuration
|
||||||
|
# 8. Scans for security issues
|
||||||
|
# 9. Provides overall summary
|
||||||
|
#
|
||||||
|
# Customize based on your stack:
|
||||||
|
# - Remove JS jobs if Python-only
|
||||||
|
# - Remove Python jobs if JS-only
|
||||||
|
# - Add database migrations check
|
||||||
|
# - Add API contract tests
|
||||||
|
# - Add performance benchmarks
|
||||||
|
#
|
||||||
|
# Status checks:
|
||||||
|
# - Configure branch protection to require these jobs
|
||||||
|
# - Prevents merging failing code
|
||||||
|
# - Ensures code quality standards
|
||||||
286
.github/workflows/templates/frontend-deploy-template.yml
vendored
Normal file
286
.github/workflows/templates/frontend-deploy-template.yml
vendored
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
# Frontend Deployment Workflow Template
|
||||||
|
# ======================================
|
||||||
|
#
|
||||||
|
# This template deploys static frontend sites to various platforms.
|
||||||
|
#
|
||||||
|
# Supported targets:
|
||||||
|
# - GitHub Pages
|
||||||
|
# - Cloudflare Pages
|
||||||
|
# - Railway static hosting
|
||||||
|
# - Vercel
|
||||||
|
# - Netlify
|
||||||
|
#
|
||||||
|
# How to use:
|
||||||
|
# -----------
|
||||||
|
# 1. Copy this file to .github/workflows/frontend-deploy.yml
|
||||||
|
# 2. Choose your deployment target (uncomment the relevant job)
|
||||||
|
# 3. Configure custom domain settings
|
||||||
|
# 4. Add required secrets
|
||||||
|
# 5. Push to trigger deployment
|
||||||
|
|
||||||
|
name: Deploy Frontend
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- 'landing/**'
|
||||||
|
- 'frontend/**'
|
||||||
|
- 'backend/static/**'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
# Only allow one deployment at a time
|
||||||
|
concurrency:
|
||||||
|
group: frontend-deploy
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# ========================================
|
||||||
|
# Option 1: Deploy to GitHub Pages
|
||||||
|
# ========================================
|
||||||
|
deploy-github-pages:
|
||||||
|
name: Deploy to GitHub Pages
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
|
||||||
|
# Grant GITHUB_TOKEN permissions
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
# Protect production environment
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# If you have a build step:
|
||||||
|
# - name: Set up Node.js
|
||||||
|
# uses: actions/setup-node@v4
|
||||||
|
# with:
|
||||||
|
# node-version: '20'
|
||||||
|
# cache: 'npm'
|
||||||
|
#
|
||||||
|
# - name: Install dependencies
|
||||||
|
# run: npm ci
|
||||||
|
#
|
||||||
|
# - name: Build site
|
||||||
|
# run: npm run build
|
||||||
|
|
||||||
|
- name: Setup Pages
|
||||||
|
uses: actions/configure-pages@v4
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-pages-artifact@v3
|
||||||
|
with:
|
||||||
|
# Update path based on your static files location
|
||||||
|
path: 'backend/static' # or 'landing/', 'dist/', etc.
|
||||||
|
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v4
|
||||||
|
|
||||||
|
- name: Deployment summary
|
||||||
|
run: |
|
||||||
|
echo "✅ Deployed to GitHub Pages"
|
||||||
|
echo "URL: ${{ steps.deployment.outputs.page_url }}"
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# Option 2: Deploy to Railway (Static)
|
||||||
|
# ========================================
|
||||||
|
# deploy-railway:
|
||||||
|
# name: Deploy to Railway
|
||||||
|
# runs-on: ubuntu-latest
|
||||||
|
# timeout-minutes: 10
|
||||||
|
#
|
||||||
|
# environment:
|
||||||
|
# name: production-frontend
|
||||||
|
# url: https://blackroad.systems
|
||||||
|
#
|
||||||
|
# steps:
|
||||||
|
# - name: Checkout code
|
||||||
|
# uses: actions/checkout@v4
|
||||||
|
#
|
||||||
|
# - name: Install Railway CLI
|
||||||
|
# run: |
|
||||||
|
# curl -fsSL https://railway.app/install.sh | sh
|
||||||
|
# echo "$HOME/.railway/bin" >> $GITHUB_PATH
|
||||||
|
#
|
||||||
|
# - name: Deploy to Railway
|
||||||
|
# env:
|
||||||
|
# RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
|
||||||
|
# run: |
|
||||||
|
# railway up --service frontend --detach
|
||||||
|
# echo "✅ Deployed to Railway"
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# Option 3: Deploy to Cloudflare Pages
|
||||||
|
# ========================================
|
||||||
|
# deploy-cloudflare-pages:
|
||||||
|
# name: Deploy to Cloudflare Pages
|
||||||
|
# runs-on: ubuntu-latest
|
||||||
|
# timeout-minutes: 10
|
||||||
|
#
|
||||||
|
# environment:
|
||||||
|
# name: cloudflare-pages
|
||||||
|
# url: https://blackroad.systems
|
||||||
|
#
|
||||||
|
# steps:
|
||||||
|
# - name: Checkout code
|
||||||
|
# uses: actions/checkout@v4
|
||||||
|
#
|
||||||
|
# # Build step if needed
|
||||||
|
# # - name: Build site
|
||||||
|
# # run: npm run build
|
||||||
|
#
|
||||||
|
# - name: Publish to Cloudflare Pages
|
||||||
|
# uses: cloudflare/pages-action@v1
|
||||||
|
# with:
|
||||||
|
# apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
|
# accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
|
# projectName: blackroad-os
|
||||||
|
# directory: backend/static # or dist/, build/, etc.
|
||||||
|
# gitHubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
#
|
||||||
|
# - name: Deployment summary
|
||||||
|
# run: echo "✅ Deployed to Cloudflare Pages"
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# Option 4: Deploy to Vercel
|
||||||
|
# ========================================
|
||||||
|
# deploy-vercel:
|
||||||
|
# name: Deploy to Vercel
|
||||||
|
# runs-on: ubuntu-latest
|
||||||
|
# timeout-minutes: 10
|
||||||
|
#
|
||||||
|
# environment:
|
||||||
|
# name: vercel-production
|
||||||
|
#
|
||||||
|
# steps:
|
||||||
|
# - name: Checkout code
|
||||||
|
# uses: actions/checkout@v4
|
||||||
|
#
|
||||||
|
# - name: Deploy to Vercel
|
||||||
|
# uses: amondnet/vercel-action@v25
|
||||||
|
# with:
|
||||||
|
# vercel-token: ${{ secrets.VERCEL_TOKEN }}
|
||||||
|
# vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
|
||||||
|
# vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
|
||||||
|
# vercel-args: '--prod'
|
||||||
|
# working-directory: ./
|
||||||
|
#
|
||||||
|
# - name: Deployment summary
|
||||||
|
# run: echo "✅ Deployed to Vercel"
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# Post-Deployment: Cache Purge
|
||||||
|
# ========================================
|
||||||
|
purge-cache:
|
||||||
|
name: Purge Cloudflare Cache
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: deploy-github-pages # Update based on your deployment job
|
||||||
|
if: success()
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Purge Cloudflare cache
|
||||||
|
env:
|
||||||
|
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
|
||||||
|
CF_ZONE_ID: ${{ secrets.CF_ZONE_ID }}
|
||||||
|
run: |
|
||||||
|
curl -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/purge_cache" \
|
||||||
|
-H "Authorization: Bearer $CF_API_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
--data '{"purge_everything":true}'
|
||||||
|
|
||||||
|
echo "✅ Cloudflare cache purged"
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# Post-Deployment: Smoke Tests
|
||||||
|
# ========================================
|
||||||
|
smoke-tests:
|
||||||
|
name: Frontend Smoke Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: deploy-github-pages # Update based on your deployment job
|
||||||
|
if: success()
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Wait for deployment propagation
|
||||||
|
run: sleep 30
|
||||||
|
|
||||||
|
- name: Test homepage loads
|
||||||
|
run: |
|
||||||
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" https://blackroad.systems/)
|
||||||
|
if [ "$HTTP_CODE" != "200" ]; then
|
||||||
|
echo "❌ Homepage returned HTTP $HTTP_CODE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✅ Homepage loads successfully"
|
||||||
|
|
||||||
|
- name: Test key pages
|
||||||
|
run: |
|
||||||
|
PAGES=(
|
||||||
|
"/"
|
||||||
|
"/architecture"
|
||||||
|
"/pricing"
|
||||||
|
"/contact"
|
||||||
|
)
|
||||||
|
|
||||||
|
for PAGE in "${PAGES[@]}"; do
|
||||||
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "https://blackroad.systems$PAGE")
|
||||||
|
if [ "$HTTP_CODE" = "200" ]; then
|
||||||
|
echo "✅ $PAGE loads successfully"
|
||||||
|
else
|
||||||
|
echo "⚠️ $PAGE returned HTTP $HTTP_CODE"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Test SSL certificate
|
||||||
|
run: |
|
||||||
|
echo | openssl s_client -servername blackroad.systems -connect blackroad.systems:443 2>/dev/null | \
|
||||||
|
openssl x509 -noout -dates
|
||||||
|
echo "✅ SSL certificate is valid"
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# Configuration Guide
|
||||||
|
# ========================================
|
||||||
|
#
|
||||||
|
# GitHub Pages Setup:
|
||||||
|
# ------------------
|
||||||
|
# 1. Repository → Settings → Pages
|
||||||
|
# 2. Source: GitHub Actions
|
||||||
|
# 3. Custom domain: blackroad.systems
|
||||||
|
# 4. CNAME file: Create in static files root
|
||||||
|
#
|
||||||
|
# Required GitHub Secrets:
|
||||||
|
# -----------------------
|
||||||
|
# - CF_API_TOKEN (for cache purge)
|
||||||
|
# - CF_ZONE_ID (for cache purge)
|
||||||
|
#
|
||||||
|
# Optional Secrets (based on target):
|
||||||
|
# ----------------------------------
|
||||||
|
# - RAILWAY_TOKEN (for Railway)
|
||||||
|
# - CLOUDFLARE_API_TOKEN (for CF Pages)
|
||||||
|
# - CLOUDFLARE_ACCOUNT_ID (for CF Pages)
|
||||||
|
# - VERCEL_TOKEN (for Vercel)
|
||||||
|
# - VERCEL_ORG_ID (for Vercel)
|
||||||
|
# - VERCEL_PROJECT_ID (for Vercel)
|
||||||
|
#
|
||||||
|
# Custom Domain:
|
||||||
|
# -------------
|
||||||
|
# 1. Add CNAME file to your static files:
|
||||||
|
# echo "blackroad.systems" > CNAME
|
||||||
|
# 2. Configure DNS (see infra/cloudflare/records.yaml)
|
||||||
|
# 3. Wait for DNS propagation (5-60 minutes)
|
||||||
|
#
|
||||||
|
# Deployment Targets Comparison:
|
||||||
|
# -----------------------------
|
||||||
|
# GitHub Pages: Free, simple, good for docs/marketing
|
||||||
|
# Cloudflare Pages: Free, global CDN, fast
|
||||||
|
# Railway: Paid, unified with backend
|
||||||
|
# Vercel: Free tier, excellent DX, edge functions
|
||||||
|
# Netlify: Free tier, form handling, split testing
|
||||||
292
.github/workflows/templates/railway-deploy-template.yml
vendored
Normal file
292
.github/workflows/templates/railway-deploy-template.yml
vendored
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
# Railway Deployment Workflow Template
|
||||||
|
# ======================================
|
||||||
|
#
|
||||||
|
# This template can be copied to any BlackRoad repository that deploys to Railway.
|
||||||
|
#
|
||||||
|
# How to use:
|
||||||
|
# -----------
|
||||||
|
# 1. Copy this file to .github/workflows/railway-deploy.yml in your repo
|
||||||
|
# 2. Update the service name and environment variables as needed
|
||||||
|
# 3. Add required GitHub secrets:
|
||||||
|
# - RAILWAY_TOKEN (get from: railway tokens create)
|
||||||
|
# - RAILWAY_SERVICE_ID (optional, for specific service targeting)
|
||||||
|
# 4. Push to main branch to trigger deployment
|
||||||
|
#
|
||||||
|
# Required GitHub Secrets:
|
||||||
|
# -----------------------
|
||||||
|
# RAILWAY_TOKEN - Railway API token for CLI authentication
|
||||||
|
#
|
||||||
|
# Optional GitHub Secrets/Variables:
|
||||||
|
# ---------------------------------
|
||||||
|
# RAILWAY_SERVICE_ID - Specific Railway service ID to deploy
|
||||||
|
# SENTRY_DSN - Sentry error monitoring DSN
|
||||||
|
#
|
||||||
|
# Customization:
|
||||||
|
# -------------
|
||||||
|
# - Change trigger branches (currently: main)
|
||||||
|
# - Add/remove build steps
|
||||||
|
# - Configure environment-specific variables
|
||||||
|
# - Add post-deploy notifications (Slack, Discord, etc.)
|
||||||
|
|
||||||
|
name: Deploy to Railway
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths-ignore:
|
||||||
|
- '**.md'
|
||||||
|
- 'docs/**'
|
||||||
|
- '.github/**'
|
||||||
|
- '!.github/workflows/railway-deploy.yml'
|
||||||
|
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
environment:
|
||||||
|
description: 'Deployment environment'
|
||||||
|
required: true
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- production
|
||||||
|
- staging
|
||||||
|
default: 'production'
|
||||||
|
|
||||||
|
# Only allow one deployment at a time
|
||||||
|
concurrency:
|
||||||
|
group: railway-deploy-${{ github.ref }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
name: Deploy to Railway
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
|
# Set deployment environment
|
||||||
|
environment:
|
||||||
|
name: ${{ github.event.inputs.environment || 'production' }}
|
||||||
|
url: https://os.blackroad.systems # Update with your actual URL
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# ========================================
|
||||||
|
# 1. Checkout code
|
||||||
|
# ========================================
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# 2. Install Railway CLI
|
||||||
|
# ========================================
|
||||||
|
- name: Install Railway CLI
|
||||||
|
run: |
|
||||||
|
curl -fsSL https://railway.app/install.sh | sh
|
||||||
|
echo "$HOME/.railway/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
|
- name: Verify Railway installation
|
||||||
|
run: railway --version
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# 3. Set up environment
|
||||||
|
# ========================================
|
||||||
|
- name: Set up environment variables
|
||||||
|
run: |
|
||||||
|
echo "RAILWAY_TOKEN=${{ secrets.RAILWAY_TOKEN }}" >> $GITHUB_ENV
|
||||||
|
echo "GIT_SHA=${GITHUB_SHA::8}" >> $GITHUB_ENV
|
||||||
|
echo "DEPLOY_TIME=$(date -u +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# 4. Pre-deploy validation (optional)
|
||||||
|
# ========================================
|
||||||
|
- name: Validate environment variables
|
||||||
|
run: |
|
||||||
|
if [ -z "${{ secrets.RAILWAY_TOKEN }}" ]; then
|
||||||
|
echo "❌ Error: RAILWAY_TOKEN secret is not set"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✅ Environment variables validated"
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# 5. Deploy to Railway
|
||||||
|
# ========================================
|
||||||
|
- name: Deploy to Railway
|
||||||
|
id: deploy
|
||||||
|
env:
|
||||||
|
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
|
||||||
|
run: |
|
||||||
|
echo "🚀 Deploying to Railway..."
|
||||||
|
echo "Environment: ${{ github.event.inputs.environment || 'production' }}"
|
||||||
|
echo "Commit: ${GITHUB_SHA::8}"
|
||||||
|
echo "Branch: ${GITHUB_REF_NAME}"
|
||||||
|
|
||||||
|
# Deploy using Railway CLI
|
||||||
|
# If RAILWAY_SERVICE_ID is set, deploy to specific service
|
||||||
|
if [ -n "${{ secrets.RAILWAY_SERVICE_ID }}" ]; then
|
||||||
|
railway up \
|
||||||
|
--service "${{ secrets.RAILWAY_SERVICE_ID }}" \
|
||||||
|
--detach
|
||||||
|
else
|
||||||
|
railway up --detach
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Deployment initiated"
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# 6. Wait for deployment and health check
|
||||||
|
# ========================================
|
||||||
|
- name: Wait for deployment
|
||||||
|
id: wait
|
||||||
|
env:
|
||||||
|
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
|
||||||
|
run: |
|
||||||
|
echo "⏳ Waiting for deployment to complete..."
|
||||||
|
|
||||||
|
# Wait up to 5 minutes for deployment
|
||||||
|
MAX_WAIT=300
|
||||||
|
ELAPSED=0
|
||||||
|
INTERVAL=10
|
||||||
|
|
||||||
|
while [ $ELAPSED -lt $MAX_WAIT ]; do
|
||||||
|
# Check deployment status (simplified - adjust based on Railway CLI output)
|
||||||
|
STATUS=$(railway status --json 2>/dev/null || echo '{"status":"unknown"}')
|
||||||
|
|
||||||
|
echo "Status check at ${ELAPSED}s: Deployment in progress..."
|
||||||
|
|
||||||
|
# Sleep and increment
|
||||||
|
sleep $INTERVAL
|
||||||
|
ELAPSED=$((ELAPSED + INTERVAL))
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "⏰ Deployment wait period completed"
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# 7. Health check (optional but recommended)
|
||||||
|
# ========================================
|
||||||
|
- name: Health check
|
||||||
|
id: health
|
||||||
|
run: |
|
||||||
|
echo "🏥 Running health check..."
|
||||||
|
|
||||||
|
# Update with your actual health endpoint
|
||||||
|
HEALTH_URL="https://os.blackroad.systems/health"
|
||||||
|
|
||||||
|
# Try health check up to 5 times
|
||||||
|
MAX_ATTEMPTS=5
|
||||||
|
ATTEMPT=1
|
||||||
|
|
||||||
|
while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
|
||||||
|
echo "Health check attempt $ATTEMPT/$MAX_ATTEMPTS..."
|
||||||
|
|
||||||
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$HEALTH_URL" || echo "000")
|
||||||
|
|
||||||
|
if [ "$HTTP_CODE" = "200" ]; then
|
||||||
|
echo "✅ Health check passed (HTTP $HTTP_CODE)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "⚠️ Health check returned HTTP $HTTP_CODE, retrying..."
|
||||||
|
sleep 10
|
||||||
|
ATTEMPT=$((ATTEMPT + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "❌ Health check failed after $MAX_ATTEMPTS attempts"
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# 8. Post-deploy notifications (optional)
|
||||||
|
# ========================================
|
||||||
|
- name: Notify deployment success
|
||||||
|
if: success()
|
||||||
|
run: |
|
||||||
|
echo "✅ Deployment successful!"
|
||||||
|
echo "SHA: ${GITHUB_SHA::8}"
|
||||||
|
echo "Time: $(date -u +'%Y-%m-%d %H:%M:%S UTC')"
|
||||||
|
|
||||||
|
# Add Slack/Discord webhook here if needed
|
||||||
|
# Example:
|
||||||
|
# curl -X POST -H 'Content-type: application/json' \
|
||||||
|
# --data '{"text":"✅ Deployed to Railway: '"${GITHUB_SHA::8}"'"}' \
|
||||||
|
# ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# 9. Handle deployment failure
|
||||||
|
# ========================================
|
||||||
|
- name: Notify deployment failure
|
||||||
|
if: failure()
|
||||||
|
run: |
|
||||||
|
echo "❌ Deployment failed!"
|
||||||
|
echo "SHA: ${GITHUB_SHA::8}"
|
||||||
|
echo "Check Railway logs for details"
|
||||||
|
|
||||||
|
# Add Slack/Discord webhook here if needed
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# 10. Send to Sentry (optional)
|
||||||
|
# ========================================
|
||||||
|
- name: Create Sentry release
|
||||||
|
if: success() && vars.SENTRY_DSN != ''
|
||||||
|
env:
|
||||||
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||||
|
SENTRY_ORG: blackroad
|
||||||
|
SENTRY_PROJECT: blackroad-os
|
||||||
|
run: |
|
||||||
|
# Install Sentry CLI
|
||||||
|
curl -sL https://sentry.io/get-cli/ | bash
|
||||||
|
|
||||||
|
# Create release
|
||||||
|
sentry-cli releases new "${GITHUB_SHA::8}"
|
||||||
|
sentry-cli releases set-commits "${GITHUB_SHA::8}" --auto
|
||||||
|
sentry-cli releases finalize "${GITHUB_SHA::8}"
|
||||||
|
sentry-cli releases deploys "${GITHUB_SHA::8}" new -e production
|
||||||
|
|
||||||
|
echo "✅ Sentry release created"
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# Optional: Smoke tests after deployment
|
||||||
|
# ========================================
|
||||||
|
smoke-tests:
|
||||||
|
name: Smoke Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: deploy
|
||||||
|
if: success()
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Run smoke tests
|
||||||
|
run: |
|
||||||
|
echo "🧪 Running smoke tests..."
|
||||||
|
|
||||||
|
# Basic smoke tests
|
||||||
|
BASE_URL="https://os.blackroad.systems"
|
||||||
|
|
||||||
|
# Test 1: Health endpoint
|
||||||
|
echo "Test 1: Health endpoint"
|
||||||
|
curl -f "$BASE_URL/health" || exit 1
|
||||||
|
|
||||||
|
# Test 2: API documentation
|
||||||
|
echo "Test 2: API documentation"
|
||||||
|
curl -f "$BASE_URL/api/docs" || exit 1
|
||||||
|
|
||||||
|
# Test 3: Frontend loads
|
||||||
|
echo "Test 3: Frontend loads"
|
||||||
|
curl -f "$BASE_URL/" || exit 1
|
||||||
|
|
||||||
|
echo "✅ All smoke tests passed"
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# Workflow Summary
|
||||||
|
# ========================================
|
||||||
|
#
|
||||||
|
# This workflow:
|
||||||
|
# 1. Triggers on push to main or manual dispatch
|
||||||
|
# 2. Installs Railway CLI
|
||||||
|
# 3. Validates environment
|
||||||
|
# 4. Deploys to Railway
|
||||||
|
# 5. Waits for deployment
|
||||||
|
# 6. Runs health checks
|
||||||
|
# 7. Sends notifications
|
||||||
|
# 8. Creates Sentry release (optional)
|
||||||
|
# 9. Runs smoke tests (optional)
|
||||||
|
#
|
||||||
|
# Customize as needed for your specific service!
|
||||||
793
ENV_VARS.md
Normal file
793
ENV_VARS.md
Normal file
@@ -0,0 +1,793 @@
|
|||||||
|
# Environment Variables Documentation
|
||||||
|
|
||||||
|
**Version:** 1.0
|
||||||
|
**Date:** 2025-11-18
|
||||||
|
**Purpose:** Complete reference for all environment variables across BlackRoad OS infrastructure
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document provides the **complete list** of environment variables used across the BlackRoad OS ecosystem. It covers:
|
||||||
|
|
||||||
|
- **Required variables** for core functionality
|
||||||
|
- **Optional variables** for integrations and features
|
||||||
|
- **Where to set them** (Railway, GitHub Actions, local development)
|
||||||
|
- **How to generate secret values** safely
|
||||||
|
- **Security best practices**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Core Application](#core-application)
|
||||||
|
2. [Database & Cache](#database--cache)
|
||||||
|
3. [Authentication & Security](#authentication--security)
|
||||||
|
4. [API Integrations](#api-integrations)
|
||||||
|
5. [Infrastructure](#infrastructure)
|
||||||
|
6. [Deployment](#deployment)
|
||||||
|
7. [Observability](#observability)
|
||||||
|
8. [Where to Set Variables](#where-to-set-variables)
|
||||||
|
9. [Security Best Practices](#security-best-practices)
|
||||||
|
10. [Quick Start Templates](#quick-start-templates)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Application
|
||||||
|
|
||||||
|
### ENVIRONMENT
|
||||||
|
**Required:** Yes
|
||||||
|
**Default:** `development`
|
||||||
|
**Valid values:** `development`, `staging`, `production`
|
||||||
|
**Description:** Runtime environment identifier
|
||||||
|
|
||||||
|
**Where to set:**
|
||||||
|
- **Railway (production):** `production`
|
||||||
|
- **Railway (staging):** `staging`
|
||||||
|
- **Local:** `development`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
ENVIRONMENT=production
|
||||||
|
```
|
||||||
|
|
||||||
|
### DEBUG
|
||||||
|
**Required:** No
|
||||||
|
**Default:** `False`
|
||||||
|
**Valid values:** `True`, `False`
|
||||||
|
**Description:** Enable debug mode (verbose logging, stack traces)
|
||||||
|
|
||||||
|
**⚠️ Security Warning:** MUST be `False` in production!
|
||||||
|
|
||||||
|
**Where to set:**
|
||||||
|
- **Railway:** `False`
|
||||||
|
- **Local:** `True` (for development)
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
DEBUG=False
|
||||||
|
```
|
||||||
|
|
||||||
|
### SECRET_KEY
|
||||||
|
**Required:** Yes
|
||||||
|
**Description:** Secret key for signing sessions, JWT tokens, and encryption
|
||||||
|
|
||||||
|
**How to generate:**
|
||||||
|
```bash
|
||||||
|
# Option 1: Using openssl
|
||||||
|
openssl rand -hex 32
|
||||||
|
|
||||||
|
# Option 2: Using Python
|
||||||
|
python -c "import secrets; print(secrets.token_hex(32))"
|
||||||
|
```
|
||||||
|
|
||||||
|
**⚠️ Security:**
|
||||||
|
- MUST be unique per environment (production ≠ staging ≠ local)
|
||||||
|
- MUST be at least 32 characters
|
||||||
|
- NEVER commit to git
|
||||||
|
- Rotate quarterly or after suspected compromise
|
||||||
|
|
||||||
|
**Where to set:**
|
||||||
|
- **Railway (production):** Generate unique value
|
||||||
|
- **Railway (staging):** Generate different unique value
|
||||||
|
- **Local:** Generate local value (in `.env`, gitignored)
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
SECRET_KEY=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6
|
||||||
|
```
|
||||||
|
|
||||||
|
### API_BASE_URL
|
||||||
|
**Required:** Yes
|
||||||
|
**Description:** Public URL for the backend API (used by frontend for API calls)
|
||||||
|
|
||||||
|
**Where to set:**
|
||||||
|
- **Railway (production):** `https://os.blackroad.systems`
|
||||||
|
- **Railway (staging):** `https://staging.blackroad.systems`
|
||||||
|
- **Local:** `http://localhost:8000`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
API_BASE_URL=https://os.blackroad.systems
|
||||||
|
```
|
||||||
|
|
||||||
|
### FRONTEND_URL
|
||||||
|
**Required:** Yes
|
||||||
|
**Description:** Public URL for the frontend (used for CORS, redirects, emails)
|
||||||
|
|
||||||
|
**Where to set:**
|
||||||
|
- **Railway (production):** `https://os.blackroad.systems`
|
||||||
|
- **Railway (staging):** `https://staging.blackroad.systems`
|
||||||
|
- **Local:** `http://localhost:8000`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
FRONTEND_URL=https://os.blackroad.systems
|
||||||
|
```
|
||||||
|
|
||||||
|
### ALLOWED_ORIGINS
|
||||||
|
**Required:** Yes
|
||||||
|
**Description:** Comma-separated list of allowed CORS origins
|
||||||
|
|
||||||
|
**Where to set:**
|
||||||
|
- **Railway (production):** `https://os.blackroad.systems,https://blackroad.ai,https://blackroad.network`
|
||||||
|
- **Railway (staging):** `https://staging.blackroad.systems`
|
||||||
|
- **Local:** `http://localhost:8000,http://localhost:3000`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
ALLOWED_ORIGINS=https://os.blackroad.systems,https://blackroad.ai
|
||||||
|
```
|
||||||
|
|
||||||
|
### PORT
|
||||||
|
**Required:** No (Railway auto-detects)
|
||||||
|
**Default:** `8000`
|
||||||
|
**Description:** HTTP port for the backend server
|
||||||
|
|
||||||
|
**Where to set:**
|
||||||
|
- **Railway:** Not needed (Railway sets `$PORT` automatically)
|
||||||
|
- **Local:** `8000` (or any available port)
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
PORT=8000
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Database & Cache
|
||||||
|
|
||||||
|
### DATABASE_URL
|
||||||
|
**Required:** Yes
|
||||||
|
**Description:** PostgreSQL connection string (sync driver)
|
||||||
|
|
||||||
|
**Format:** `postgresql://user:password@host:port/database`
|
||||||
|
|
||||||
|
**Where to set:**
|
||||||
|
- **Railway:** Auto-injected via `${{Postgres.DATABASE_URL}}`
|
||||||
|
- **Local:** `postgresql://user:pass@localhost:5432/blackroad_dev`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
DATABASE_URL=postgresql://user:password@localhost:5432/blackroad_dev
|
||||||
|
```
|
||||||
|
|
||||||
|
**How to use Railway reference:**
|
||||||
|
In Railway dashboard → Service → Variables:
|
||||||
|
```
|
||||||
|
DATABASE_URL=${{Postgres.DATABASE_URL}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### DATABASE_ASYNC_URL
|
||||||
|
**Required:** Yes (for async database operations)
|
||||||
|
**Description:** PostgreSQL connection string (async driver)
|
||||||
|
|
||||||
|
**Format:** `postgresql+asyncpg://user:password@host:port/database`
|
||||||
|
|
||||||
|
**Where to set:**
|
||||||
|
- **Railway:** Convert `DATABASE_URL` to async:
|
||||||
|
```
|
||||||
|
DATABASE_ASYNC_URL=${{Postgres.DATABASE_URL_ASYNC}}
|
||||||
|
```
|
||||||
|
Or manually construct:
|
||||||
|
```bash
|
||||||
|
# If Railway doesn't provide async URL, derive from sync URL
|
||||||
|
# Replace 'postgresql://' with 'postgresql+asyncpg://'
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Local:** `postgresql+asyncpg://user:pass@localhost:5432/blackroad_dev`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
DATABASE_ASYNC_URL=postgresql+asyncpg://user:password@localhost:5432/blackroad_dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### REDIS_URL
|
||||||
|
**Required:** Yes
|
||||||
|
**Description:** Redis connection string for caching and sessions
|
||||||
|
|
||||||
|
**Format:** `redis://host:port/db` or `redis://user:password@host:port/db`
|
||||||
|
|
||||||
|
**Where to set:**
|
||||||
|
- **Railway:** Auto-injected via `${{Redis.REDIS_URL}}`
|
||||||
|
- **Local:** `redis://localhost:6379/0`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
REDIS_URL=redis://localhost:6379/0
|
||||||
|
```
|
||||||
|
|
||||||
|
**How to use Railway reference:**
|
||||||
|
In Railway dashboard → Service → Variables:
|
||||||
|
```
|
||||||
|
REDIS_URL=${{Redis.REDIS_URL}}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Authentication & Security
|
||||||
|
|
||||||
|
### ACCESS_TOKEN_EXPIRE_MINUTES
|
||||||
|
**Required:** No
|
||||||
|
**Default:** `30`
|
||||||
|
**Description:** JWT access token expiration time (in minutes)
|
||||||
|
|
||||||
|
**Recommended values:**
|
||||||
|
- **Production:** `15` - `30` (shorter = more secure)
|
||||||
|
- **Development:** `60` - `120` (longer = less login friction)
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
ACCESS_TOKEN_EXPIRE_MINUTES=30
|
||||||
|
```
|
||||||
|
|
||||||
|
### REFRESH_TOKEN_EXPIRE_DAYS
|
||||||
|
**Required:** No
|
||||||
|
**Default:** `7`
|
||||||
|
**Description:** JWT refresh token expiration time (in days)
|
||||||
|
|
||||||
|
**Recommended values:**
|
||||||
|
- **Production:** `7` - `14`
|
||||||
|
- **Development:** `30`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
REFRESH_TOKEN_EXPIRE_DAYS=7
|
||||||
|
```
|
||||||
|
|
||||||
|
### WALLET_MASTER_KEY
|
||||||
|
**Required:** Yes (for blockchain features)
|
||||||
|
**Description:** Master key for wallet/blockchain operations
|
||||||
|
|
||||||
|
**How to generate:**
|
||||||
|
```bash
|
||||||
|
openssl rand -hex 32
|
||||||
|
```
|
||||||
|
|
||||||
|
**⚠️ Security:**
|
||||||
|
- CRITICAL: Losing this key means losing access to blockchain wallets
|
||||||
|
- Backup securely (encrypted password manager, hardware security module)
|
||||||
|
- NEVER expose in logs or error messages
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
WALLET_MASTER_KEY=your-generated-master-key-here
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API Integrations
|
||||||
|
|
||||||
|
### OPENAI_API_KEY
|
||||||
|
**Required:** For AI features (Lucidia, agents)
|
||||||
|
**Description:** OpenAI API key for GPT models
|
||||||
|
|
||||||
|
**How to get:**
|
||||||
|
1. Go to https://platform.openai.com/api-keys
|
||||||
|
2. Create new secret key
|
||||||
|
3. Copy key (starts with `sk-`)
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
OPENAI_API_KEY=sk-proj-1234567890abcdef...
|
||||||
|
```
|
||||||
|
|
||||||
|
### ANTHROPIC_API_KEY
|
||||||
|
**Required:** For Claude integration
|
||||||
|
**Description:** Anthropic API key for Claude models
|
||||||
|
|
||||||
|
**How to get:**
|
||||||
|
1. Go to https://console.anthropic.com/settings/keys
|
||||||
|
2. Create new API key
|
||||||
|
3. Copy key
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
ANTHROPIC_API_KEY=sk-ant-1234567890abcdef...
|
||||||
|
```
|
||||||
|
|
||||||
|
### GITHUB_TOKEN
|
||||||
|
**Required:** For GitHub integrations (agents, automation)
|
||||||
|
**Description:** GitHub Personal Access Token (PAT)
|
||||||
|
|
||||||
|
**How to get:**
|
||||||
|
1. Go to https://github.com/settings/tokens
|
||||||
|
2. Generate new token (classic or fine-grained)
|
||||||
|
3. Required scopes: `repo`, `workflow`, `read:org`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
GITHUB_TOKEN=ghp_1234567890abcdefghijklmnopqrstuvwxyz
|
||||||
|
```
|
||||||
|
|
||||||
|
### STRIPE_SECRET_KEY
|
||||||
|
**Required:** For payment features
|
||||||
|
**Description:** Stripe API secret key
|
||||||
|
|
||||||
|
**How to get:**
|
||||||
|
1. Go to https://dashboard.stripe.com/apikeys
|
||||||
|
2. Copy "Secret key" (starts with `sk_test_` or `sk_live_`)
|
||||||
|
|
||||||
|
**⚠️ Use test key for development:**
|
||||||
|
```bash
|
||||||
|
# Development/Staging
|
||||||
|
STRIPE_SECRET_KEY=sk_test_...
|
||||||
|
|
||||||
|
# Production
|
||||||
|
STRIPE_SECRET_KEY=sk_live_...
|
||||||
|
```
|
||||||
|
|
||||||
|
### STRIPE_PUBLISHABLE_KEY
|
||||||
|
**Required:** For frontend payment UI
|
||||||
|
**Description:** Stripe publishable key (safe to expose in frontend)
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
STRIPE_PUBLISHABLE_KEY=pk_test_1234567890abcdef...
|
||||||
|
```
|
||||||
|
|
||||||
|
### AWS_ACCESS_KEY_ID
|
||||||
|
**Required:** For AWS S3 storage
|
||||||
|
**Description:** AWS IAM access key ID
|
||||||
|
|
||||||
|
**How to get:**
|
||||||
|
1. AWS Console → IAM → Users → Security Credentials
|
||||||
|
2. Create access key
|
||||||
|
3. Download/save credentials
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
|
||||||
|
```
|
||||||
|
|
||||||
|
### AWS_SECRET_ACCESS_KEY
|
||||||
|
**Required:** For AWS S3 storage
|
||||||
|
**Description:** AWS IAM secret access key
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
|
||||||
|
```
|
||||||
|
|
||||||
|
### AWS_S3_BUCKET
|
||||||
|
**Required:** For AWS S3 storage
|
||||||
|
**Description:** S3 bucket name for file uploads
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
AWS_S3_BUCKET=blackroad-uploads-production
|
||||||
|
```
|
||||||
|
|
||||||
|
### AWS_REGION
|
||||||
|
**Required:** For AWS S3 storage
|
||||||
|
**Default:** `us-east-1`
|
||||||
|
**Description:** AWS region for S3 bucket
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
AWS_REGION=us-east-1
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Infrastructure
|
||||||
|
|
||||||
|
### CF_API_TOKEN
|
||||||
|
**Required:** For Cloudflare automation
|
||||||
|
**Description:** Cloudflare API token for DNS/cache management
|
||||||
|
|
||||||
|
**How to get:**
|
||||||
|
1. Cloudflare dashboard → My Profile → API Tokens
|
||||||
|
2. Create token with "Zone.DNS" edit permissions
|
||||||
|
3. Copy token
|
||||||
|
|
||||||
|
**Where to set:**
|
||||||
|
- **GitHub Actions:** As secret `CF_API_TOKEN`
|
||||||
|
- **Local:** Export when running DNS scripts
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
CF_API_TOKEN=your-cloudflare-api-token
|
||||||
|
```
|
||||||
|
|
||||||
|
### CF_ZONE_ID
|
||||||
|
**Required:** For Cloudflare automation
|
||||||
|
**Description:** Cloudflare zone ID for a specific domain
|
||||||
|
|
||||||
|
**How to get:**
|
||||||
|
1. Cloudflare dashboard → Select domain
|
||||||
|
2. Overview page → Right sidebar → Zone ID
|
||||||
|
|
||||||
|
**Where to set:**
|
||||||
|
- **GitHub Actions:** As secret `CF_ZONE_ID`
|
||||||
|
- **records.yaml:** In domain configuration
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
CF_ZONE_ID=1234567890abcdef1234567890abcdef
|
||||||
|
```
|
||||||
|
|
||||||
|
### RAILWAY_TOKEN
|
||||||
|
**Required:** For Railway CLI/CI deployments
|
||||||
|
**Description:** Railway API token for deployments
|
||||||
|
|
||||||
|
**How to get:**
|
||||||
|
```bash
|
||||||
|
railway login
|
||||||
|
railway tokens create
|
||||||
|
```
|
||||||
|
|
||||||
|
**Where to set:**
|
||||||
|
- **GitHub Actions:** As secret `RAILWAY_TOKEN`
|
||||||
|
- **Local:** Export when using Railway CLI
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
RAILWAY_TOKEN=your-railway-api-token
|
||||||
|
```
|
||||||
|
|
||||||
|
### RAILWAY_SERVICE_ID
|
||||||
|
**Required:** For specific service deployments
|
||||||
|
**Description:** Railway service ID to deploy to
|
||||||
|
|
||||||
|
**How to get:**
|
||||||
|
1. Railway dashboard → Service → Settings
|
||||||
|
2. Copy Service ID
|
||||||
|
|
||||||
|
**Where to set:**
|
||||||
|
- **GitHub Actions:** As repository variable
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
RAILWAY_SERVICE_ID=abc123def456
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
### SENTRY_DSN
|
||||||
|
**Required:** For error monitoring
|
||||||
|
**Description:** Sentry Data Source Name for error tracking
|
||||||
|
|
||||||
|
**How to get:**
|
||||||
|
1. Go to https://sentry.io
|
||||||
|
2. Create new project
|
||||||
|
3. Copy DSN from project settings
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
SENTRY_DSN=https://1234567890abcdef@o123456.ingest.sentry.io/1234567
|
||||||
|
```
|
||||||
|
|
||||||
|
### SENTRY_ENVIRONMENT
|
||||||
|
**Required:** No (if using SENTRY_DSN)
|
||||||
|
**Default:** Uses `ENVIRONMENT` value
|
||||||
|
**Description:** Sentry environment tag
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
SENTRY_ENVIRONMENT=production
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Observability
|
||||||
|
|
||||||
|
### LOG_LEVEL
|
||||||
|
**Required:** No
|
||||||
|
**Default:** `INFO`
|
||||||
|
**Valid values:** `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`
|
||||||
|
**Description:** Application logging level
|
||||||
|
|
||||||
|
**Recommended:**
|
||||||
|
- **Production:** `WARNING` or `INFO`
|
||||||
|
- **Staging:** `INFO` or `DEBUG`
|
||||||
|
- **Development:** `DEBUG`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
LOG_LEVEL=INFO
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Where to Set Variables
|
||||||
|
|
||||||
|
### Railway (Production & Staging)
|
||||||
|
|
||||||
|
1. Go to Railway dashboard → Your project
|
||||||
|
2. Select service (e.g., `backend`)
|
||||||
|
3. Go to **Variables** tab
|
||||||
|
4. Click **New Variable**
|
||||||
|
5. Enter name and value
|
||||||
|
6. Click **Add**
|
||||||
|
|
||||||
|
**Railway Reference Variables:**
|
||||||
|
Use `${{ServiceName.VARIABLE}}` to reference values from other services:
|
||||||
|
```
|
||||||
|
DATABASE_URL=${{Postgres.DATABASE_URL}}
|
||||||
|
REDIS_URL=${{Redis.REDIS_URL}}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Shared Variables:**
|
||||||
|
For variables used across services, use **Shared Variables**:
|
||||||
|
1. Variables tab → **Shared Variables**
|
||||||
|
2. Add variable (e.g., `SECRET_KEY`)
|
||||||
|
3. All services can access
|
||||||
|
|
||||||
|
### GitHub Actions
|
||||||
|
|
||||||
|
1. Go to repository → Settings → Secrets and variables → Actions
|
||||||
|
2. Click **New repository secret**
|
||||||
|
3. Enter name and value
|
||||||
|
4. Click **Add secret**
|
||||||
|
|
||||||
|
**Required GitHub Secrets:**
|
||||||
|
- `RAILWAY_TOKEN` - For Railway deployments
|
||||||
|
- `CF_API_TOKEN` - For DNS automation
|
||||||
|
- `SENTRY_DSN` - For error monitoring (optional)
|
||||||
|
|
||||||
|
**How to use in workflows:**
|
||||||
|
```yaml
|
||||||
|
env:
|
||||||
|
RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Local Development
|
||||||
|
|
||||||
|
1. Copy environment template:
|
||||||
|
```bash
|
||||||
|
cp backend/.env.example backend/.env
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Edit `.env` file with your local values:
|
||||||
|
```bash
|
||||||
|
nano backend/.env # or use your preferred editor
|
||||||
|
```
|
||||||
|
|
||||||
|
3. The `.env` file is gitignored - safe to add real values
|
||||||
|
|
||||||
|
4. FastAPI automatically loads `.env` via `python-dotenv`
|
||||||
|
|
||||||
|
**⚠️ Never commit `.env` files to git!**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security Best Practices
|
||||||
|
|
||||||
|
### 1. Secret Generation
|
||||||
|
- **Always use cryptographically secure random generators**
|
||||||
|
- Minimum 32 characters for secrets
|
||||||
|
- Use different secrets per environment
|
||||||
|
|
||||||
|
**Good:**
|
||||||
|
```bash
|
||||||
|
openssl rand -hex 32
|
||||||
|
python -c "import secrets; print(secrets.token_hex(32))"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Bad:**
|
||||||
|
```bash
|
||||||
|
# Don't use predictable values
|
||||||
|
SECRET_KEY=password123
|
||||||
|
SECRET_KEY=my-app-secret
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Secret Storage
|
||||||
|
- ✅ Railway environment variables (encrypted at rest)
|
||||||
|
- ✅ GitHub Actions secrets (encrypted)
|
||||||
|
- ✅ `.env` files (gitignored)
|
||||||
|
- ✅ Password managers (1Password, LastPass, Bitwarden)
|
||||||
|
- ❌ NEVER commit secrets to git
|
||||||
|
- ❌ NEVER hardcode in source code
|
||||||
|
- ❌ NEVER expose in logs or error messages
|
||||||
|
|
||||||
|
### 3. Secret Rotation
|
||||||
|
- Rotate `SECRET_KEY` quarterly
|
||||||
|
- Rotate API keys after suspected compromise
|
||||||
|
- Update credentials after team member departure
|
||||||
|
- Keep backups of old secrets (for rollback)
|
||||||
|
|
||||||
|
### 4. Principle of Least Privilege
|
||||||
|
- Give each service only the permissions it needs
|
||||||
|
- Use separate database users for different services
|
||||||
|
- Use read-only API keys where possible
|
||||||
|
- Limit token scopes (GitHub, Stripe, etc.)
|
||||||
|
|
||||||
|
### 5. Verification
|
||||||
|
Before deploying to production:
|
||||||
|
- [ ] All required variables are set
|
||||||
|
- [ ] No placeholder values (e.g., `REPLACE_ME`)
|
||||||
|
- [ ] Secrets are unique per environment
|
||||||
|
- [ ] `DEBUG=False` in production
|
||||||
|
- [ ] CORS origins match production domains
|
||||||
|
- [ ] Database backups are configured
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Start Templates
|
||||||
|
|
||||||
|
### Production Railway (backend service)
|
||||||
|
```bash
|
||||||
|
# Core
|
||||||
|
ENVIRONMENT=production
|
||||||
|
DEBUG=False
|
||||||
|
SECRET_KEY=[generate-unique-32-char-string]
|
||||||
|
API_BASE_URL=https://os.blackroad.systems
|
||||||
|
FRONTEND_URL=https://os.blackroad.systems
|
||||||
|
ALLOWED_ORIGINS=https://os.blackroad.systems,https://blackroad.ai
|
||||||
|
|
||||||
|
# Database (auto-injected by Railway)
|
||||||
|
DATABASE_URL=${{Postgres.DATABASE_URL}}
|
||||||
|
DATABASE_ASYNC_URL=${{Postgres.DATABASE_URL_ASYNC}}
|
||||||
|
REDIS_URL=${{Redis.REDIS_URL}}
|
||||||
|
|
||||||
|
# Auth
|
||||||
|
ACCESS_TOKEN_EXPIRE_MINUTES=30
|
||||||
|
REFRESH_TOKEN_EXPIRE_DAYS=7
|
||||||
|
WALLET_MASTER_KEY=[generate-unique-32-char-string]
|
||||||
|
|
||||||
|
# AI (add when ready)
|
||||||
|
# OPENAI_API_KEY=sk-proj-...
|
||||||
|
# ANTHROPIC_API_KEY=sk-ant-...
|
||||||
|
|
||||||
|
# Observability (add when ready)
|
||||||
|
# SENTRY_DSN=https://...
|
||||||
|
# LOG_LEVEL=WARNING
|
||||||
|
```
|
||||||
|
|
||||||
|
### Local Development (.env)
|
||||||
|
```bash
|
||||||
|
# Core
|
||||||
|
ENVIRONMENT=development
|
||||||
|
DEBUG=True
|
||||||
|
SECRET_KEY=local-dev-secret-key-not-for-production
|
||||||
|
API_BASE_URL=http://localhost:8000
|
||||||
|
FRONTEND_URL=http://localhost:8000
|
||||||
|
ALLOWED_ORIGINS=http://localhost:8000,http://localhost:3000
|
||||||
|
|
||||||
|
# Database (Docker Compose)
|
||||||
|
DATABASE_URL=postgresql://blackroad:blackroad@localhost:5432/blackroad_dev
|
||||||
|
DATABASE_ASYNC_URL=postgresql+asyncpg://blackroad:blackroad@localhost:5432/blackroad_dev
|
||||||
|
REDIS_URL=redis://localhost:6379/0
|
||||||
|
|
||||||
|
# Auth
|
||||||
|
ACCESS_TOKEN_EXPIRE_MINUTES=120
|
||||||
|
REFRESH_TOKEN_EXPIRE_DAYS=30
|
||||||
|
WALLET_MASTER_KEY=local-dev-wallet-key
|
||||||
|
|
||||||
|
# AI (optional - use your own keys)
|
||||||
|
# OPENAI_API_KEY=sk-...
|
||||||
|
# ANTHROPIC_API_KEY=sk-ant-...
|
||||||
|
```
|
||||||
|
|
||||||
|
### GitHub Actions Secrets
|
||||||
|
```bash
|
||||||
|
# Required
|
||||||
|
RAILWAY_TOKEN=[get-from-railway-cli]
|
||||||
|
CF_API_TOKEN=[get-from-cloudflare]
|
||||||
|
|
||||||
|
# Optional
|
||||||
|
SENTRY_DSN=[get-from-sentry]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Validation Script
|
||||||
|
|
||||||
|
Use this script to validate your environment variables:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# File: scripts/validate_env.sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Validating environment variables..."
|
||||||
|
|
||||||
|
# Check required variables
|
||||||
|
REQUIRED_VARS=(
|
||||||
|
"ENVIRONMENT"
|
||||||
|
"SECRET_KEY"
|
||||||
|
"DATABASE_URL"
|
||||||
|
"REDIS_URL"
|
||||||
|
"API_BASE_URL"
|
||||||
|
"FRONTEND_URL"
|
||||||
|
"ALLOWED_ORIGINS"
|
||||||
|
)
|
||||||
|
|
||||||
|
MISSING_VARS=()
|
||||||
|
|
||||||
|
for VAR in "${REQUIRED_VARS[@]}"; do
|
||||||
|
if [ -z "${!VAR}" ]; then
|
||||||
|
MISSING_VARS+=("$VAR")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ${#MISSING_VARS[@]} -gt 0 ]; then
|
||||||
|
echo "❌ Missing required environment variables:"
|
||||||
|
printf ' - %s\n' "${MISSING_VARS[@]}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check SECRET_KEY length
|
||||||
|
if [ ${#SECRET_KEY} -lt 32 ]; then
|
||||||
|
echo "❌ SECRET_KEY must be at least 32 characters"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check DEBUG in production
|
||||||
|
if [ "$ENVIRONMENT" = "production" ] && [ "$DEBUG" = "True" ]; then
|
||||||
|
echo "⚠️ WARNING: DEBUG=True in production environment!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Environment variables validated successfully!"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Run before deploy:**
|
||||||
|
```bash
|
||||||
|
chmod +x scripts/validate_env.sh
|
||||||
|
source backend/.env && ./scripts/validate_env.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Variable Not Loading
|
||||||
|
|
||||||
|
**Problem:** Application doesn't see environment variable
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. **Railway:** Check service Variables tab - is variable set?
|
||||||
|
2. **Local:** Is `.env` file in correct location (`backend/.env`)?
|
||||||
|
3. **Restart:** Restart application after changing variables
|
||||||
|
4. **Syntax:** Check for typos in variable names
|
||||||
|
|
||||||
|
### Database Connection Fails
|
||||||
|
|
||||||
|
**Problem:** `DatabaseConnectionError` or similar
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. Check `DATABASE_URL` format is correct
|
||||||
|
2. Railway: Verify Postgres plugin is attached
|
||||||
|
3. Local: Check Docker Compose is running (`docker-compose ps`)
|
||||||
|
4. Check database credentials are correct
|
||||||
|
|
||||||
|
### CORS Errors
|
||||||
|
|
||||||
|
**Problem:** Frontend can't call API (CORS error in browser console)
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. Check `ALLOWED_ORIGINS` includes frontend domain
|
||||||
|
2. Include protocol: `https://` not just `domain.com`
|
||||||
|
3. No trailing slash: `https://domain.com` not `https://domain.com/`
|
||||||
|
4. Railway: Wait 1-2 minutes for variable updates to apply
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**This document is the single source of truth for environment variables. Keep it updated as new variables are added.**
|
||||||
|
|
||||||
|
**Where AI meets the open road.** 🛣️
|
||||||
@@ -1049,6 +1049,77 @@ Month 12: First paying customers ✅
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## PHASE 1 IMPLEMENTATION FILES
|
||||||
|
|
||||||
|
The master orchestration plan is now backed by **concrete implementation files**:
|
||||||
|
|
||||||
|
### Infrastructure Implementation
|
||||||
|
|
||||||
|
**Cloudflare DNS:**
|
||||||
|
- `infra/cloudflare/records.yaml` - Complete DNS configuration for all domains
|
||||||
|
- `infra/cloudflare/migrate_to_cloudflare.md` - Step-by-step migration guide for Alexa
|
||||||
|
- `infra/cloudflare/cloudflare_dns_sync.py` - Automated DNS sync script (Python)
|
||||||
|
- `infra/cloudflare/CLOUDFLARE_DNS_BLUEPRINT.md` - Updated with implementation references
|
||||||
|
|
||||||
|
**Environment Variables:**
|
||||||
|
- `ENV_VARS.md` - Complete environment variable documentation for all services
|
||||||
|
- Covers: Railway, GitHub Actions, Cloudflare, local development
|
||||||
|
- Includes: Security best practices, validation scripts, troubleshooting
|
||||||
|
|
||||||
|
**Frontend Infrastructure:**
|
||||||
|
- `infra/frontend/LANDING_PAGE_PLAN.md` - Detailed landing page implementation plan
|
||||||
|
- Includes: Page structure, design system, content guidelines, deployment strategy
|
||||||
|
|
||||||
|
### GitHub Actions Workflows
|
||||||
|
|
||||||
|
**Templates** (in `.github/workflows/templates/`):
|
||||||
|
- `railway-deploy-template.yml` - Railway deployment workflow
|
||||||
|
- `frontend-deploy-template.yml` - Frontend deployment (GitHub Pages, Cloudflare Pages, etc.)
|
||||||
|
- `codeql-analysis-template.yml` - Security scanning workflow
|
||||||
|
- `comprehensive-ci-template.yml` - Complete CI pipeline (lint, test, build)
|
||||||
|
|
||||||
|
**Active Configuration:**
|
||||||
|
- `.github/dependabot.yml` - Automated dependency updates
|
||||||
|
|
||||||
|
**How to use templates:**
|
||||||
|
1. Copy template from `.github/workflows/templates/` to `.github/workflows/`
|
||||||
|
2. Remove `-template` from filename
|
||||||
|
3. Customize for your specific repo/service
|
||||||
|
4. Commit and push
|
||||||
|
|
||||||
|
### Deployment Documentation
|
||||||
|
|
||||||
|
**Environment Map:**
|
||||||
|
- `infra/env/ENVIRONMENT_MAP.md` - Existing cross-platform variable map
|
||||||
|
|
||||||
|
**New Documentation:**
|
||||||
|
- `ENV_VARS.md` - Comprehensive variable reference
|
||||||
|
- Railway workflow templates with health checks
|
||||||
|
- Frontend deployment options (GitHub Pages, Railway, Cloudflare Pages)
|
||||||
|
|
||||||
|
### Next Steps for Implementation
|
||||||
|
|
||||||
|
**Immediate (Week 1-2):**
|
||||||
|
1. Follow `infra/cloudflare/migrate_to_cloudflare.md` to migrate DNS
|
||||||
|
2. Set up GitHub secrets (CF_API_TOKEN, RAILWAY_TOKEN)
|
||||||
|
3. Copy workflow templates to active `.github/workflows/` directory
|
||||||
|
4. Deploy backend to Railway with custom domains
|
||||||
|
5. Deploy frontend to GitHub Pages (or Railway)
|
||||||
|
|
||||||
|
**Short-term (Week 3-4):**
|
||||||
|
1. Implement landing page using `infra/frontend/LANDING_PAGE_PLAN.md`
|
||||||
|
2. Set up CodeQL security scanning
|
||||||
|
3. Configure Dependabot for automated updates
|
||||||
|
4. Create first design partner case study (placeholder)
|
||||||
|
|
||||||
|
**Medium-term (Month 2-3):**
|
||||||
|
1. Refine based on real traffic and feedback
|
||||||
|
2. Add analytics (Google Analytics/Plausible)
|
||||||
|
3. Create email drip campaign for demo requests
|
||||||
|
4. Launch private alpha
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## READY FOR THE NEXT COMMAND, OPERATOR.
|
## READY FOR THE NEXT COMMAND, OPERATOR.
|
||||||
|
|
||||||
This master plan synthesizes:
|
This master plan synthesizes:
|
||||||
@@ -1060,12 +1131,15 @@ This master plan synthesizes:
|
|||||||
- ✅ Domain strategy (10+ domains with routing)
|
- ✅ Domain strategy (10+ domains with routing)
|
||||||
- ✅ Agent orchestration (Atlas/Operator/Prism/Lucidia)
|
- ✅ Agent orchestration (Atlas/Operator/Prism/Lucidia)
|
||||||
- ✅ Brand architecture (all sub-brands)
|
- ✅ Brand architecture (all sub-brands)
|
||||||
|
- ✅ **PHASE 1 IMPLEMENTATION** (infrastructure files, workflows, documentation)
|
||||||
|
|
||||||
**Your next concrete action**: Start with Alexa's Next-Action Checklist, Item #1 (Migrate DNS to Cloudflare).
|
**Your next concrete action**: Start with `infra/cloudflare/migrate_to_cloudflare.md` - migrate DNS to Cloudflare.
|
||||||
|
|
||||||
**Your north star**: Phase 1, Q1 deliverables - get the foundation solid, then build on it.
|
**Your north star**: Phase 1, Q1 deliverables - get the foundation solid, then build on it.
|
||||||
|
|
||||||
**Your superpower**: You have the complete vision documented. Now execute methodically, one step at a time.
|
**Your superpower**: You have the complete vision documented AND implemented. Now execute methodically, one step at a time.
|
||||||
|
|
||||||
|
**Implementation status**: ✅ Phase 1 infrastructure files ready for deployment
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -335,118 +335,132 @@ After DNS setup for each domain:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Automation Script
|
## Implementation Files
|
||||||
|
|
||||||
**File**: `scripts/cloudflare/sync_dns.py`
|
This blueprint is now implemented in the following files:
|
||||||
|
|
||||||
```python
|
### 1. DNS Records Configuration
|
||||||
#!/usr/bin/env python3
|
**File**: `infra/cloudflare/records.yaml`
|
||||||
"""
|
|
||||||
Sync DNS records from ops/domains.yaml to Cloudflare
|
|
||||||
|
|
||||||
Usage:
|
This file is the **single source of truth** for all DNS records across all BlackRoad domains. It includes:
|
||||||
export CF_API_TOKEN="your-token"
|
- Complete record definitions (type, name, content, TTL, proxy status)
|
||||||
export CF_ZONE_ID="your-zone-id"
|
- Domain metadata (zone ID, phase, priority)
|
||||||
python scripts/cloudflare/sync_dns.py
|
- Human-readable comments for each record
|
||||||
"""
|
- Organized by phase (Phase 1 active domains, Phase 2 future domains)
|
||||||
|
|
||||||
import os
|
**Quick reference**: See `records.yaml` for the exact DNS configuration to apply.
|
||||||
import sys
|
|
||||||
import yaml
|
|
||||||
import requests
|
|
||||||
from typing import Dict, List
|
|
||||||
|
|
||||||
CF_API_TOKEN = os.getenv("CF_API_TOKEN")
|
### 2. Migration Guide
|
||||||
CF_ZONE_ID = os.getenv("CF_ZONE_ID")
|
**File**: `infra/cloudflare/migrate_to_cloudflare.md`
|
||||||
CF_API_BASE = "https://api.cloudflare.com/client/v4"
|
|
||||||
|
|
||||||
def load_domains() -> Dict:
|
A **step-by-step guide** for migrating DNS from GoDaddy to Cloudflare. Designed for human operators (Alexa) with:
|
||||||
"""Load domain config from ops/domains.yaml"""
|
- Detailed instructions with screenshots references
|
||||||
with open("ops/domains.yaml") as f:
|
- Mobile-friendly (can be done from iPhone)
|
||||||
return yaml.safe_load(f)
|
- Troubleshooting section
|
||||||
|
- Verification steps
|
||||||
|
- Checklist for tracking progress
|
||||||
|
|
||||||
def get_existing_records(zone_id: str) -> List[Dict]:
|
**Quick reference**: Follow this guide to migrate each domain from GoDaddy DNS to Cloudflare DNS.
|
||||||
"""Fetch all DNS records for a zone"""
|
|
||||||
url = f"{CF_API_BASE}/zones/{zone_id}/dns_records"
|
|
||||||
headers = {
|
|
||||||
"Authorization": f"Bearer {CF_API_TOKEN}",
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
response = requests.get(url, headers=headers)
|
|
||||||
response.raise_for_status()
|
|
||||||
return response.json()["result"]
|
|
||||||
|
|
||||||
def create_dns_record(zone_id: str, record: Dict) -> Dict:
|
### 3. Automation Script
|
||||||
"""Create a DNS record"""
|
**File**: `infra/cloudflare/cloudflare_dns_sync.py`
|
||||||
url = f"{CF_API_BASE}/zones/{zone_id}/dns_records"
|
|
||||||
headers = {
|
|
||||||
"Authorization": f"Bearer {CF_API_TOKEN}",
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
response = requests.post(url, headers=headers, json=record)
|
|
||||||
response.raise_for_status()
|
|
||||||
return response.json()["result"]
|
|
||||||
|
|
||||||
def update_dns_record(zone_id: str, record_id: str, record: Dict) -> Dict:
|
An **idempotent Python script** that syncs DNS records from `records.yaml` to Cloudflare via API. Features:
|
||||||
"""Update a DNS record"""
|
- Reads structured YAML configuration
|
||||||
url = f"{CF_API_BASE}/zones/{zone_id}/dns_records/{record_id}"
|
- Creates, updates, and optionally deletes DNS records
|
||||||
headers = {
|
- Dry-run mode for safe testing
|
||||||
"Authorization": f"Bearer {CF_API_TOKEN}",
|
- Domain and phase filtering
|
||||||
"Content-Type": "application/json"
|
- Comprehensive logging
|
||||||
}
|
|
||||||
response = requests.put(url, headers=headers, json=record)
|
|
||||||
response.raise_for_status()
|
|
||||||
return response.json()["result"]
|
|
||||||
|
|
||||||
def sync_records():
|
**Usage**:
|
||||||
"""Sync DNS records from domains.yaml to Cloudflare"""
|
|
||||||
if not CF_API_TOKEN or not CF_ZONE_ID:
|
|
||||||
print("Error: CF_API_TOKEN and CF_ZONE_ID must be set")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
config = load_domains()
|
|
||||||
existing = get_existing_records(CF_ZONE_ID)
|
|
||||||
|
|
||||||
# Build index of existing records
|
|
||||||
existing_index = {
|
|
||||||
f"{r['type']}:{r['name']}": r for r in existing
|
|
||||||
}
|
|
||||||
|
|
||||||
for domain in config.get("domains", []):
|
|
||||||
if domain.get("mode") != "dns":
|
|
||||||
continue
|
|
||||||
|
|
||||||
record_data = {
|
|
||||||
"type": domain["record"]["type"],
|
|
||||||
"name": domain["name"],
|
|
||||||
"content": domain["record"]["value"],
|
|
||||||
"ttl": 1, # Auto
|
|
||||||
"proxied": True # Enable Cloudflare proxy
|
|
||||||
}
|
|
||||||
|
|
||||||
key = f"{record_data['type']}:{record_data['name']}"
|
|
||||||
|
|
||||||
if key in existing_index:
|
|
||||||
# Update existing
|
|
||||||
record_id = existing_index[key]["id"]
|
|
||||||
print(f"Updating: {key}")
|
|
||||||
update_dns_record(CF_ZONE_ID, record_id, record_data)
|
|
||||||
else:
|
|
||||||
# Create new
|
|
||||||
print(f"Creating: {key}")
|
|
||||||
create_dns_record(CF_ZONE_ID, record_data)
|
|
||||||
|
|
||||||
print("✅ DNS sync complete!")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sync_records()
|
|
||||||
```
|
|
||||||
|
|
||||||
**Make executable**:
|
|
||||||
```bash
|
```bash
|
||||||
chmod +x scripts/cloudflare/sync_dns.py
|
# Set Cloudflare API token
|
||||||
|
export CF_API_TOKEN="your-cloudflare-api-token"
|
||||||
|
|
||||||
|
# Dry run (safe - shows changes without applying)
|
||||||
|
python infra/cloudflare/cloudflare_dns_sync.py --dry-run
|
||||||
|
|
||||||
|
# Sync specific domain
|
||||||
|
python infra/cloudflare/cloudflare_dns_sync.py --domain blackroad.systems
|
||||||
|
|
||||||
|
# Sync all Phase 1 domains
|
||||||
|
python infra/cloudflare/cloudflare_dns_sync.py --phase 1
|
||||||
|
|
||||||
|
# Apply all changes
|
||||||
|
python infra/cloudflare/cloudflare_dns_sync.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Requirements**:
|
||||||
|
```bash
|
||||||
|
pip install pyyaml requests
|
||||||
|
```
|
||||||
|
|
||||||
|
## How These Files Connect to Railway + GitHub
|
||||||
|
|
||||||
|
### DNS → Railway Flow
|
||||||
|
|
||||||
|
1. **Cloudflare DNS** receives user request for `os.blackroad.systems`
|
||||||
|
2. **CNAME record** (from `records.yaml`) points to `blackroad-os-production.up.railway.app`
|
||||||
|
3. **Cloudflare CDN** proxies request (SSL, caching, DDoS protection)
|
||||||
|
4. **Railway** receives request and routes to FastAPI backend
|
||||||
|
5. **FastAPI** serves Pocket OS frontend from `backend/static/`
|
||||||
|
|
||||||
|
### Railway Custom Domains
|
||||||
|
|
||||||
|
For each subdomain pointing to Railway, you must also:
|
||||||
|
1. Add the custom domain in **Railway dashboard**:
|
||||||
|
- Service → Settings → Networking → Custom Domains
|
||||||
|
- Add domain (e.g., `os.blackroad.systems`)
|
||||||
|
- Railway auto-provisions SSL certificate (Let's Encrypt)
|
||||||
|
|
||||||
|
2. Wait for Railway to show **green checkmark** (SSL ready)
|
||||||
|
|
||||||
|
3. Verify: Visit `https://os.blackroad.systems` - should show valid SSL 🔒
|
||||||
|
|
||||||
|
### GitHub Actions Integration
|
||||||
|
|
||||||
|
The DNS sync script can be automated via GitHub Actions:
|
||||||
|
|
||||||
|
**Workflow file** (create at `.github/workflows/dns-sync.yml`):
|
||||||
|
```yaml
|
||||||
|
name: Sync Cloudflare DNS
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- 'infra/cloudflare/records.yaml'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
sync-dns:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pip install pyyaml requests
|
||||||
|
|
||||||
|
- name: Sync DNS records
|
||||||
|
env:
|
||||||
|
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
|
||||||
|
run: |
|
||||||
|
python infra/cloudflare/cloudflare_dns_sync.py --phase 1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Required GitHub Secrets**:
|
||||||
|
- `CF_API_TOKEN` - Cloudflare API token with Zone.DNS edit permissions
|
||||||
|
|
||||||
|
**How it works**:
|
||||||
|
1. Push changes to `records.yaml`
|
||||||
|
2. GitHub Action runs automatically
|
||||||
|
3. Script syncs DNS records to Cloudflare
|
||||||
|
4. Changes are live within seconds
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|||||||
484
infra/cloudflare/cloudflare_dns_sync.py
Executable file
484
infra/cloudflare/cloudflare_dns_sync.py
Executable file
@@ -0,0 +1,484 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Cloudflare DNS Sync Script
|
||||||
|
===========================
|
||||||
|
|
||||||
|
Syncs DNS records from records.yaml to Cloudflare via API.
|
||||||
|
This script is idempotent - safe to run multiple times.
|
||||||
|
|
||||||
|
How to run:
|
||||||
|
-----------
|
||||||
|
1. Get your Cloudflare API token:
|
||||||
|
- Go to Cloudflare dashboard → My Profile → API Tokens
|
||||||
|
- Create token with "Zone.DNS" edit permissions
|
||||||
|
- Copy the token
|
||||||
|
|
||||||
|
2. Get your zone IDs:
|
||||||
|
- Go to each domain in Cloudflare dashboard
|
||||||
|
- Copy the Zone ID from the Overview page (right sidebar)
|
||||||
|
- Update records.yaml with the zone IDs
|
||||||
|
|
||||||
|
3. Set environment variables:
|
||||||
|
export CF_API_TOKEN="your-cloudflare-api-token"
|
||||||
|
|
||||||
|
4. Run the script:
|
||||||
|
python infra/cloudflare/cloudflare_dns_sync.py
|
||||||
|
|
||||||
|
Optional flags:
|
||||||
|
--------------
|
||||||
|
--dry-run Show what would change without making changes
|
||||||
|
--domain NAME Only sync specific domain (e.g., blackroad.systems)
|
||||||
|
--phase N Only sync domains in specific phase (1, 2, etc.)
|
||||||
|
--delete-extra Delete DNS records not in records.yaml (use carefully!)
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
---------
|
||||||
|
# Dry run (safe - shows changes without applying)
|
||||||
|
python infra/cloudflare/cloudflare_dns_sync.py --dry-run
|
||||||
|
|
||||||
|
# Sync only blackroad.systems
|
||||||
|
python infra/cloudflare/cloudflare_dns_sync.py --domain blackroad.systems
|
||||||
|
|
||||||
|
# Sync only Phase 1 domains
|
||||||
|
python infra/cloudflare/cloudflare_dns_sync.py --phase 1
|
||||||
|
|
||||||
|
# Sync and delete extra records (DANGEROUS - be careful!)
|
||||||
|
python infra/cloudflare/cloudflare_dns_sync.py --delete-extra
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
------------
|
||||||
|
pip install pyyaml requests
|
||||||
|
|
||||||
|
Author: BlackRoad OS Infrastructure Team
|
||||||
|
Version: 1.0
|
||||||
|
Date: 2025-11-18
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
from typing import Dict, List, Tuple
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
try:
|
||||||
|
import yaml
|
||||||
|
except ImportError:
|
||||||
|
print("Error: pyyaml is not installed. Run: pip install pyyaml")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import requests
|
||||||
|
except ImportError:
|
||||||
|
print("Error: requests is not installed. Run: pip install requests")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S'
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Cloudflare API configuration
|
||||||
|
CF_API_BASE = "https://api.cloudflare.com/client/v4"
|
||||||
|
CF_API_TOKEN = os.getenv("CF_API_TOKEN")
|
||||||
|
|
||||||
|
|
||||||
|
class CloudflareAPI:
|
||||||
|
"""Simple wrapper for Cloudflare API calls."""
|
||||||
|
|
||||||
|
def __init__(self, api_token: str):
|
||||||
|
if not api_token:
|
||||||
|
raise ValueError("CF_API_TOKEN environment variable not set")
|
||||||
|
|
||||||
|
self.api_token = api_token
|
||||||
|
self.session = requests.Session()
|
||||||
|
self.session.headers.update({
|
||||||
|
"Authorization": f"Bearer {api_token}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
})
|
||||||
|
|
||||||
|
def _request(self, method: str, endpoint: str, **kwargs) -> Dict:
|
||||||
|
"""Make a request to Cloudflare API."""
|
||||||
|
url = f"{CF_API_BASE}{endpoint}"
|
||||||
|
response = self.session.request(method, url, **kwargs)
|
||||||
|
|
||||||
|
# Parse JSON response
|
||||||
|
try:
|
||||||
|
data = response.json()
|
||||||
|
except ValueError:
|
||||||
|
logger.error(f"Invalid JSON response from {url}")
|
||||||
|
response.raise_for_status()
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# Check for API errors
|
||||||
|
if not data.get("success", False):
|
||||||
|
errors = data.get("errors", [])
|
||||||
|
error_msg = ", ".join([e.get("message", "Unknown error") for e in errors])
|
||||||
|
logger.error(f"Cloudflare API error: {error_msg}")
|
||||||
|
raise Exception(f"Cloudflare API error: {error_msg}")
|
||||||
|
|
||||||
|
return data.get("result", {})
|
||||||
|
|
||||||
|
def get_dns_records(self, zone_id: str) -> List[Dict]:
|
||||||
|
"""Get all DNS records for a zone."""
|
||||||
|
logger.info(f"Fetching DNS records for zone {zone_id}")
|
||||||
|
|
||||||
|
records = []
|
||||||
|
page = 1
|
||||||
|
per_page = 100
|
||||||
|
|
||||||
|
while True:
|
||||||
|
result = self._request(
|
||||||
|
"GET",
|
||||||
|
f"/zones/{zone_id}/dns_records",
|
||||||
|
params={"page": page, "per_page": per_page}
|
||||||
|
)
|
||||||
|
|
||||||
|
if isinstance(result, list):
|
||||||
|
records.extend(result)
|
||||||
|
if len(result) < per_page:
|
||||||
|
break
|
||||||
|
page += 1
|
||||||
|
elif isinstance(result, dict):
|
||||||
|
# Newer API returns paginated result
|
||||||
|
records.extend(result.get("result", []))
|
||||||
|
info = result.get("result_info", {})
|
||||||
|
if info.get("page", 1) >= info.get("total_pages", 1):
|
||||||
|
break
|
||||||
|
page += 1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
logger.info(f"Found {len(records)} existing DNS records")
|
||||||
|
return records
|
||||||
|
|
||||||
|
def create_dns_record(self, zone_id: str, record: Dict) -> Dict:
|
||||||
|
"""Create a new DNS record."""
|
||||||
|
logger.info(f"Creating {record['type']} record: {record['name']} → {record['content']}")
|
||||||
|
return self._request("POST", f"/zones/{zone_id}/dns_records", json=record)
|
||||||
|
|
||||||
|
def update_dns_record(self, zone_id: str, record_id: str, record: Dict) -> Dict:
|
||||||
|
"""Update an existing DNS record."""
|
||||||
|
logger.info(f"Updating {record['type']} record: {record['name']} → {record['content']}")
|
||||||
|
return self._request("PUT", f"/zones/{zone_id}/dns_records/{record_id}", json=record)
|
||||||
|
|
||||||
|
def delete_dns_record(self, zone_id: str, record_id: str) -> Dict:
|
||||||
|
"""Delete a DNS record."""
|
||||||
|
logger.warning(f"Deleting DNS record: {record_id}")
|
||||||
|
return self._request("DELETE", f"/zones/{zone_id}/dns_records/{record_id}")
|
||||||
|
|
||||||
|
|
||||||
|
def load_records_config() -> List[Dict]:
|
||||||
|
"""Load DNS records from records.yaml."""
|
||||||
|
config_path = Path(__file__).parent / "records.yaml"
|
||||||
|
|
||||||
|
if not config_path.exists():
|
||||||
|
logger.error(f"Config file not found: {config_path}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
logger.info(f"Loading DNS configuration from {config_path}")
|
||||||
|
|
||||||
|
with open(config_path, 'r') as f:
|
||||||
|
data = yaml.safe_load(f)
|
||||||
|
|
||||||
|
if not isinstance(data, list):
|
||||||
|
logger.error("Invalid records.yaml format - expected list of domains")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_record_name(name: str, domain: str) -> str:
|
||||||
|
"""Normalize record name for comparison."""
|
||||||
|
if name == '@':
|
||||||
|
return domain
|
||||||
|
elif name.endswith(f'.{domain}'):
|
||||||
|
return name
|
||||||
|
else:
|
||||||
|
return f"{name}.{domain}"
|
||||||
|
|
||||||
|
|
||||||
|
def records_equal(r1: Dict, r2: Dict) -> bool:
|
||||||
|
"""Check if two DNS records are functionally equal."""
|
||||||
|
# Compare essential fields
|
||||||
|
if r1.get('type') != r2.get('type'):
|
||||||
|
return False
|
||||||
|
if r1.get('content') != r2.get('content'):
|
||||||
|
return False
|
||||||
|
if r1.get('proxied', False) != r2.get('proxied', False):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# For MX records, also compare priority
|
||||||
|
if r1.get('type') == 'MX':
|
||||||
|
if r1.get('priority') != r2.get('priority'):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def build_cloudflare_record(record: Dict, domain: str) -> Dict:
|
||||||
|
"""Build a Cloudflare API record payload from our config format."""
|
||||||
|
cf_record = {
|
||||||
|
'type': record['type'],
|
||||||
|
'name': normalize_record_name(record['name'], domain),
|
||||||
|
'content': record['content'],
|
||||||
|
'ttl': record.get('ttl', 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add proxied flag (not for MX, TXT, some others)
|
||||||
|
if record['type'] in ['A', 'AAAA', 'CNAME']:
|
||||||
|
cf_record['proxied'] = record.get('proxied', False)
|
||||||
|
|
||||||
|
# Add priority for MX records
|
||||||
|
if record['type'] == 'MX':
|
||||||
|
cf_record['priority'] = record.get('priority', 10)
|
||||||
|
|
||||||
|
# Add comment if supported (newer Cloudflare API)
|
||||||
|
if 'comment' in record:
|
||||||
|
cf_record['comment'] = record['comment']
|
||||||
|
|
||||||
|
return cf_record
|
||||||
|
|
||||||
|
|
||||||
|
def sync_domain(
|
||||||
|
api: CloudflareAPI,
|
||||||
|
domain_config: Dict,
|
||||||
|
dry_run: bool = False,
|
||||||
|
delete_extra: bool = False
|
||||||
|
) -> Tuple[int, int, int, int]:
|
||||||
|
"""
|
||||||
|
Sync DNS records for a single domain.
|
||||||
|
Returns: (created, updated, deleted, unchanged) counts
|
||||||
|
"""
|
||||||
|
domain = domain_config['domain']
|
||||||
|
zone_id = domain_config.get('zone_id', '')
|
||||||
|
desired_records = domain_config.get('records', [])
|
||||||
|
|
||||||
|
logger.info(f"\n{'='*60}")
|
||||||
|
logger.info(f"Syncing domain: {domain}")
|
||||||
|
logger.info(f"Zone ID: {zone_id}")
|
||||||
|
logger.info(f"Desired records: {len(desired_records)}")
|
||||||
|
logger.info(f"{'='*60}")
|
||||||
|
|
||||||
|
if not zone_id or 'REPLACE' in zone_id:
|
||||||
|
logger.error(f"Skipping {domain} - Zone ID not configured in records.yaml")
|
||||||
|
return (0, 0, 0, 0)
|
||||||
|
|
||||||
|
# Get existing records from Cloudflare
|
||||||
|
try:
|
||||||
|
existing_records = api.get_dns_records(zone_id)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to fetch DNS records for {domain}: {e}")
|
||||||
|
return (0, 0, 0, 0)
|
||||||
|
|
||||||
|
# Build index of existing records
|
||||||
|
existing_index = {}
|
||||||
|
for record in existing_records:
|
||||||
|
key = f"{record['type']}:{record['name']}"
|
||||||
|
existing_index[key] = record
|
||||||
|
|
||||||
|
# Track changes
|
||||||
|
created = 0
|
||||||
|
updated = 0
|
||||||
|
deleted = 0
|
||||||
|
unchanged = 0
|
||||||
|
|
||||||
|
# Process desired records
|
||||||
|
for desired_record in desired_records:
|
||||||
|
cf_record = build_cloudflare_record(desired_record, domain)
|
||||||
|
record_type = cf_record['type']
|
||||||
|
record_name = cf_record['name']
|
||||||
|
key = f"{record_type}:{record_name}"
|
||||||
|
|
||||||
|
if key in existing_index:
|
||||||
|
# Record exists - check if update needed
|
||||||
|
existing = existing_index[key]
|
||||||
|
|
||||||
|
if records_equal(cf_record, existing):
|
||||||
|
logger.debug(f"✓ Unchanged: {key}")
|
||||||
|
unchanged += 1
|
||||||
|
else:
|
||||||
|
logger.info(f"↻ Update needed: {key}")
|
||||||
|
logger.info(f" Current: {existing.get('content')}")
|
||||||
|
logger.info(f" Desired: {cf_record.get('content')}")
|
||||||
|
|
||||||
|
if not dry_run:
|
||||||
|
try:
|
||||||
|
api.update_dns_record(zone_id, existing['id'], cf_record)
|
||||||
|
updated += 1
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to update {key}: {e}")
|
||||||
|
else:
|
||||||
|
logger.info(f" [DRY RUN] Would update record")
|
||||||
|
updated += 1
|
||||||
|
|
||||||
|
# Mark as processed
|
||||||
|
del existing_index[key]
|
||||||
|
else:
|
||||||
|
# Record doesn't exist - create it
|
||||||
|
logger.info(f"+ Create needed: {key}")
|
||||||
|
|
||||||
|
if not dry_run:
|
||||||
|
try:
|
||||||
|
api.create_dns_record(zone_id, cf_record)
|
||||||
|
created += 1
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to create {key}: {e}")
|
||||||
|
else:
|
||||||
|
logger.info(f" [DRY RUN] Would create record")
|
||||||
|
created += 1
|
||||||
|
|
||||||
|
# Handle extra records not in config
|
||||||
|
if existing_index:
|
||||||
|
logger.info(f"\nFound {len(existing_index)} extra records not in config:")
|
||||||
|
for key, record in existing_index.items():
|
||||||
|
logger.info(f" - {key} → {record.get('content')}")
|
||||||
|
|
||||||
|
if delete_extra:
|
||||||
|
logger.warning("Deleting extra records (--delete-extra flag set)")
|
||||||
|
for key, record in existing_index.items():
|
||||||
|
if not dry_run:
|
||||||
|
try:
|
||||||
|
api.delete_dns_record(zone_id, record['id'])
|
||||||
|
deleted += 1
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to delete {key}: {e}")
|
||||||
|
else:
|
||||||
|
logger.warning(f" [DRY RUN] Would delete {key}")
|
||||||
|
deleted += 1
|
||||||
|
else:
|
||||||
|
logger.info(" (Use --delete-extra to remove these records)")
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
logger.info(f"\nSummary for {domain}:")
|
||||||
|
logger.info(f" Created: {created}")
|
||||||
|
logger.info(f" Updated: {updated}")
|
||||||
|
logger.info(f" Deleted: {deleted}")
|
||||||
|
logger.info(f" Unchanged: {unchanged}")
|
||||||
|
|
||||||
|
return (created, updated, deleted, unchanged)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point."""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Sync DNS records from records.yaml to Cloudflare",
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
epilog=__doc__
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--dry-run",
|
||||||
|
action="store_true",
|
||||||
|
help="Show what would change without making changes"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--domain",
|
||||||
|
type=str,
|
||||||
|
help="Only sync specific domain (e.g., blackroad.systems)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--phase",
|
||||||
|
type=int,
|
||||||
|
choices=[1, 2, 3],
|
||||||
|
help="Only sync domains in specific phase"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--delete-extra",
|
||||||
|
action="store_true",
|
||||||
|
help="Delete DNS records not in records.yaml (use carefully!)"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--verbose",
|
||||||
|
action="store_true",
|
||||||
|
help="Enable verbose logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.verbose:
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
if args.dry_run:
|
||||||
|
logger.info("🔍 DRY RUN MODE - No changes will be made\n")
|
||||||
|
|
||||||
|
# Load configuration
|
||||||
|
try:
|
||||||
|
domains = load_records_config()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to load configuration: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Initialize API client
|
||||||
|
try:
|
||||||
|
api = CloudflareAPI(CF_API_TOKEN)
|
||||||
|
except ValueError as e:
|
||||||
|
logger.error(str(e))
|
||||||
|
logger.error("\nTo get your Cloudflare API token:")
|
||||||
|
logger.error("1. Go to Cloudflare dashboard → My Profile → API Tokens")
|
||||||
|
logger.error("2. Create token with 'Zone.DNS' edit permissions")
|
||||||
|
logger.error("3. Set environment variable: export CF_API_TOKEN='your-token'")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Filter domains
|
||||||
|
filtered_domains = []
|
||||||
|
for domain_config in domains:
|
||||||
|
# Filter by specific domain
|
||||||
|
if args.domain and domain_config['domain'] != args.domain:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Filter by phase
|
||||||
|
if args.phase and domain_config.get('phase') != args.phase:
|
||||||
|
continue
|
||||||
|
|
||||||
|
filtered_domains.append(domain_config)
|
||||||
|
|
||||||
|
if not filtered_domains:
|
||||||
|
logger.warning("No domains matched your filters")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
logger.info(f"Processing {len(filtered_domains)} domain(s)\n")
|
||||||
|
|
||||||
|
# Sync each domain
|
||||||
|
total_created = 0
|
||||||
|
total_updated = 0
|
||||||
|
total_deleted = 0
|
||||||
|
total_unchanged = 0
|
||||||
|
|
||||||
|
for domain_config in filtered_domains:
|
||||||
|
created, updated, deleted, unchanged = sync_domain(
|
||||||
|
api,
|
||||||
|
domain_config,
|
||||||
|
dry_run=args.dry_run,
|
||||||
|
delete_extra=args.delete_extra
|
||||||
|
)
|
||||||
|
|
||||||
|
total_created += created
|
||||||
|
total_updated += updated
|
||||||
|
total_deleted += deleted
|
||||||
|
total_unchanged += unchanged
|
||||||
|
|
||||||
|
# Final summary
|
||||||
|
logger.info(f"\n{'='*60}")
|
||||||
|
logger.info("OVERALL SUMMARY")
|
||||||
|
logger.info(f"{'='*60}")
|
||||||
|
logger.info(f"Domains processed: {len(filtered_domains)}")
|
||||||
|
logger.info(f"Records created: {total_created}")
|
||||||
|
logger.info(f"Records updated: {total_updated}")
|
||||||
|
logger.info(f"Records deleted: {total_deleted}")
|
||||||
|
logger.info(f"Records unchanged: {total_unchanged}")
|
||||||
|
logger.info(f"{'='*60}")
|
||||||
|
|
||||||
|
if args.dry_run:
|
||||||
|
logger.info("\n🔍 This was a DRY RUN - no changes were made")
|
||||||
|
logger.info("Run without --dry-run to apply changes")
|
||||||
|
else:
|
||||||
|
logger.info("\n✅ DNS sync complete!")
|
||||||
|
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
578
infra/cloudflare/migrate_to_cloudflare.md
Normal file
578
infra/cloudflare/migrate_to_cloudflare.md
Normal file
@@ -0,0 +1,578 @@
|
|||||||
|
# Migrate DNS to Cloudflare - Step-by-Step Guide
|
||||||
|
|
||||||
|
**Version:** 1.0
|
||||||
|
**Date:** 2025-11-18
|
||||||
|
**For:** Alexa Louise Amundson (Cadillac)
|
||||||
|
**Time Required:** 30-60 minutes per domain
|
||||||
|
**Difficulty:** Beginner-friendly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This guide will walk you through migrating DNS management from GoDaddy to Cloudflare for all BlackRoad domains. This migration will give you:
|
||||||
|
|
||||||
|
- ✅ **Free SSL certificates** (automatic)
|
||||||
|
- ✅ **CDN** (content delivery network for faster loading)
|
||||||
|
- ✅ **DDoS protection** (automatic security)
|
||||||
|
- ✅ **Better performance** (global anycast network)
|
||||||
|
- ✅ **Advanced features** (Workers, Zero Trust, analytics)
|
||||||
|
|
||||||
|
**Important**: You'll keep your domain registered with GoDaddy. We're only moving the DNS management to Cloudflare.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Before you start, make sure you have:
|
||||||
|
|
||||||
|
- [x] Access to your **GoDaddy account** (where domains are registered)
|
||||||
|
- [x] A **Cloudflare account** (create free at https://cloudflare.com if you don't have one)
|
||||||
|
- [x] Access to your **Railway account** (to get your app URLs)
|
||||||
|
- [x] About **30-60 minutes** per domain (can do one at a time)
|
||||||
|
- [x] Your **iPhone** or computer (both work fine)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 1: Get Your Railway App URLs
|
||||||
|
|
||||||
|
Before configuring DNS, you need to know your Railway app URLs.
|
||||||
|
|
||||||
|
### Step 1.1: Log into Railway
|
||||||
|
|
||||||
|
1. Go to https://railway.app
|
||||||
|
2. Log in with your account
|
||||||
|
3. Select your **BlackRoad-Operating-System** project
|
||||||
|
|
||||||
|
### Step 1.2: Find Your Production Backend URL
|
||||||
|
|
||||||
|
1. Click on your **backend** service
|
||||||
|
2. Click **Settings** → **Networking**
|
||||||
|
3. Look for **Public Networking** section
|
||||||
|
4. You'll see a URL like: `blackroad-os-production.up.railway.app`
|
||||||
|
5. **Copy this URL** - you'll need it later
|
||||||
|
|
||||||
|
**Write it down here:**
|
||||||
|
```
|
||||||
|
Production Backend URL: _________________________________
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 1.3: Find Your Staging Backend URL (if exists)
|
||||||
|
|
||||||
|
If you have a staging environment:
|
||||||
|
1. Select your staging service
|
||||||
|
2. Repeat the same steps
|
||||||
|
3. Copy the staging URL
|
||||||
|
|
||||||
|
**Write it down here:**
|
||||||
|
```
|
||||||
|
Staging Backend URL: _________________________________
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 2: Add Your First Domain to Cloudflare
|
||||||
|
|
||||||
|
We'll start with **blackroad.systems** (the main domain). Once you understand the process, you can repeat for other domains.
|
||||||
|
|
||||||
|
### Step 2.1: Add Site to Cloudflare
|
||||||
|
|
||||||
|
1. Log into **Cloudflare** (https://dash.cloudflare.com)
|
||||||
|
2. Click **"Add a site"** (big blue button on the right)
|
||||||
|
3. Enter your domain: `blackroad.systems`
|
||||||
|
4. Click **"Add site"**
|
||||||
|
|
||||||
|
### Step 2.2: Select Free Plan
|
||||||
|
|
||||||
|
1. Cloudflare shows you plan options
|
||||||
|
2. Select **"Free"** plan ($0/month)
|
||||||
|
3. Click **"Continue"**
|
||||||
|
|
||||||
|
### Step 2.3: Review DNS Records
|
||||||
|
|
||||||
|
1. Cloudflare automatically **scans** your existing DNS records from GoDaddy
|
||||||
|
2. You'll see a list of records it found
|
||||||
|
3. Review them - **don't worry if it doesn't look perfect** yet
|
||||||
|
4. Click **"Continue"** at the bottom
|
||||||
|
|
||||||
|
**Note:** We'll configure the correct DNS records later using `records.yaml`.
|
||||||
|
|
||||||
|
### Step 2.4: Get Your Cloudflare Nameservers
|
||||||
|
|
||||||
|
This is the most important step!
|
||||||
|
|
||||||
|
1. Cloudflare will show you **2 nameservers** like:
|
||||||
|
```
|
||||||
|
aaaa.ns.cloudflare.com
|
||||||
|
bbbb.ns.cloudflare.com
|
||||||
|
```
|
||||||
|
(The actual names will be different - they're unique to your account)
|
||||||
|
|
||||||
|
2. **Write them down carefully:**
|
||||||
|
```
|
||||||
|
Nameserver 1: _________________________________
|
||||||
|
Nameserver 2: _________________________________
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Keep this tab open** - you'll come back to it!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 3: Update Nameservers at GoDaddy
|
||||||
|
|
||||||
|
Now we'll tell GoDaddy to use Cloudflare's nameservers instead of its own.
|
||||||
|
|
||||||
|
### Step 3.1: Log into GoDaddy
|
||||||
|
|
||||||
|
1. Open a **new tab** (keep Cloudflare tab open)
|
||||||
|
2. Go to https://godaddy.com
|
||||||
|
3. Log in with your account
|
||||||
|
4. Click **"My Domains"** (or go to https://dcc.godaddy.com/manage/domains)
|
||||||
|
|
||||||
|
### Step 3.2: Manage Domain Settings
|
||||||
|
|
||||||
|
1. Find **blackroad.systems** in your domain list
|
||||||
|
2. Click the **three dots** (•••) next to it
|
||||||
|
3. Click **"Manage DNS"**
|
||||||
|
|
||||||
|
### Step 3.3: Change Nameservers
|
||||||
|
|
||||||
|
1. Scroll down to the **"Nameservers"** section
|
||||||
|
2. Click **"Change"** button
|
||||||
|
3. Select **"I'll use my own nameservers"** (or "Custom")
|
||||||
|
4. You'll see text boxes for nameservers
|
||||||
|
|
||||||
|
5. **Enter your Cloudflare nameservers:**
|
||||||
|
- Nameserver 1: (paste the first Cloudflare nameserver)
|
||||||
|
- Nameserver 2: (paste the second Cloudflare nameserver)
|
||||||
|
|
||||||
|
6. Click **"Save"**
|
||||||
|
|
||||||
|
7. GoDaddy will show a warning: *"Changing nameservers will affect your DNS..."*
|
||||||
|
- This is normal - click **"Continue"** or **"Yes, I'm sure"**
|
||||||
|
|
||||||
|
**Important:** DNS propagation can take 5-60 minutes. Usually it's faster (5-15 minutes).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 4: Verify DNS is Active in Cloudflare
|
||||||
|
|
||||||
|
Now we wait for DNS propagation and verify everything works.
|
||||||
|
|
||||||
|
### Step 4.1: Return to Cloudflare Tab
|
||||||
|
|
||||||
|
1. Go back to your **Cloudflare tab**
|
||||||
|
2. After changing nameservers, click **"Done, check nameservers"**
|
||||||
|
3. Cloudflare will start checking (this can take 5-60 minutes)
|
||||||
|
|
||||||
|
### Step 4.2: Wait for "Active" Status
|
||||||
|
|
||||||
|
**Option A: Wait for Email**
|
||||||
|
- Cloudflare will email you when the domain is active
|
||||||
|
- Subject: "Your site is now active on a Cloudflare Free plan"
|
||||||
|
|
||||||
|
**Option B: Check Dashboard**
|
||||||
|
1. Go to Cloudflare dashboard → Your site
|
||||||
|
2. Look at the status at the top
|
||||||
|
3. Wait for it to change from **"Pending"** to **"Active"**
|
||||||
|
|
||||||
|
**While you wait:** This is a good time to grab coffee ☕ or work on something else for 15-30 minutes.
|
||||||
|
|
||||||
|
### Step 4.3: Verify with DNS Checker
|
||||||
|
|
||||||
|
Once Cloudflare shows "Active", verify DNS is working:
|
||||||
|
|
||||||
|
1. Go to https://dnschecker.org
|
||||||
|
2. Enter your domain: `blackroad.systems`
|
||||||
|
3. Select record type: **CNAME** (or A)
|
||||||
|
4. Click **"Search"**
|
||||||
|
5. You should see Cloudflare IPs across different locations
|
||||||
|
|
||||||
|
**Green checkmarks** = DNS has propagated in that region 🎉
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 5: Configure DNS Records
|
||||||
|
|
||||||
|
Now that Cloudflare is managing your DNS, let's set up the correct records.
|
||||||
|
|
||||||
|
### Step 5.1: Get Your Zone ID
|
||||||
|
|
||||||
|
1. In Cloudflare dashboard, select **blackroad.systems**
|
||||||
|
2. Scroll down on the **Overview** page
|
||||||
|
3. On the right sidebar, find **"Zone ID"**
|
||||||
|
4. Click to **copy** it
|
||||||
|
|
||||||
|
**Write it down:**
|
||||||
|
```
|
||||||
|
Zone ID for blackroad.systems: _________________________________
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5.2: Update records.yaml
|
||||||
|
|
||||||
|
1. Open `infra/cloudflare/records.yaml` in your code editor
|
||||||
|
2. Find the `blackroad.systems` section
|
||||||
|
3. Replace `REPLACE_WITH_ZONE_ID_FROM_CLOUDFLARE` with your actual Zone ID
|
||||||
|
4. Replace `blackroad-os-production.up.railway.app` with your actual Railway URL (from Part 1)
|
||||||
|
5. **Save the file**
|
||||||
|
|
||||||
|
### Step 5.3: Configure Records Manually (Option A - Browser)
|
||||||
|
|
||||||
|
**For each record in `records.yaml`:**
|
||||||
|
|
||||||
|
1. Go to Cloudflare → **DNS** → **Records**
|
||||||
|
2. Click **"Add record"**
|
||||||
|
3. Fill in:
|
||||||
|
- **Type**: (e.g., CNAME)
|
||||||
|
- **Name**: (e.g., @ for root, www for www subdomain)
|
||||||
|
- **Target**: (e.g., your Railway URL)
|
||||||
|
- **Proxy status**: Click the cloud icon to make it **orange** (proxied) ✅
|
||||||
|
- **TTL**: Select **Auto**
|
||||||
|
4. Click **"Save"**
|
||||||
|
5. Repeat for all records
|
||||||
|
|
||||||
|
**Pro tip:** Delete any old records Cloudflare imported from GoDaddy that you don't need.
|
||||||
|
|
||||||
|
### Step 5.4: Configure Records Automatically (Option B - Script)
|
||||||
|
|
||||||
|
If you're comfortable with command line:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Set your Cloudflare credentials
|
||||||
|
export CF_API_TOKEN="your-cloudflare-api-token"
|
||||||
|
export CF_ZONE_ID="your-zone-id"
|
||||||
|
|
||||||
|
# Run the sync script
|
||||||
|
python infra/cloudflare/cloudflare_dns_sync.py
|
||||||
|
```
|
||||||
|
|
||||||
|
See `cloudflare_dns_sync.py` documentation for details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 6: Configure SSL/TLS
|
||||||
|
|
||||||
|
Cloudflare provides free SSL certificates, but we need to configure the encryption mode.
|
||||||
|
|
||||||
|
### Step 6.1: Set SSL/TLS Mode
|
||||||
|
|
||||||
|
1. In Cloudflare dashboard, go to **SSL/TLS** (left menu)
|
||||||
|
2. Under **Overview**, set encryption mode to:
|
||||||
|
- **"Full (strict)"** ✅
|
||||||
|
|
||||||
|
**Why?** This ensures end-to-end encryption:
|
||||||
|
- User → Cloudflare: Encrypted with Cloudflare cert
|
||||||
|
- Cloudflare → Railway: Encrypted with Railway cert
|
||||||
|
|
||||||
|
3. Cloudflare saves this automatically
|
||||||
|
|
||||||
|
### Step 6.2: Enable Always Use HTTPS
|
||||||
|
|
||||||
|
1. Go to **SSL/TLS** → **Edge Certificates**
|
||||||
|
2. Turn on **"Always Use HTTPS"** ✅
|
||||||
|
- This redirects all HTTP traffic to HTTPS automatically
|
||||||
|
|
||||||
|
3. Turn on **"Automatic HTTPS Rewrites"** ✅
|
||||||
|
- This fixes mixed content warnings
|
||||||
|
|
||||||
|
4. Turn on **"Certificate Transparency Monitoring"** ✅
|
||||||
|
- This monitors your SSL certificate health
|
||||||
|
|
||||||
|
### Step 6.3: Enable HSTS (Optional but Recommended)
|
||||||
|
|
||||||
|
1. Scroll down to **"HTTP Strict Transport Security (HSTS)"**
|
||||||
|
2. Click **"Enable HSTS"**
|
||||||
|
3. Read the warning, then configure:
|
||||||
|
- **Max Age**: 6 months (15768000 seconds)
|
||||||
|
- **Include subdomains**: ✅ Yes
|
||||||
|
- **Preload**: ❌ No (enable this later when stable)
|
||||||
|
4. Click **"Next"** → **"I understand"**
|
||||||
|
|
||||||
|
**Warning:** HSTS is a security feature that forces browsers to only use HTTPS. Don't enable "Preload" until you're 100% sure SSL is working perfectly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 7: Optimize Performance
|
||||||
|
|
||||||
|
Let's configure caching and performance features.
|
||||||
|
|
||||||
|
### Step 7.1: Enable Auto Minify
|
||||||
|
|
||||||
|
1. Go to **Speed** → **Optimization** (left menu)
|
||||||
|
2. Under **Auto Minify**, check:
|
||||||
|
- ✅ JavaScript
|
||||||
|
- ✅ CSS
|
||||||
|
- ✅ HTML
|
||||||
|
3. Cloudflare saves automatically
|
||||||
|
|
||||||
|
### Step 7.2: Enable Brotli Compression
|
||||||
|
|
||||||
|
1. In the same **Speed** → **Optimization** page
|
||||||
|
2. Turn on **"Brotli"** ✅
|
||||||
|
- This compresses your files even more than gzip
|
||||||
|
|
||||||
|
### Step 7.3: Set Caching Level
|
||||||
|
|
||||||
|
1. Go to **Caching** → **Configuration**
|
||||||
|
2. Set **Caching Level** to **"Standard"**
|
||||||
|
3. Set **Browser Cache TTL** to **"Respect Existing Headers"**
|
||||||
|
- This lets your backend control cache timing
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 8: Add Custom Domain to Railway
|
||||||
|
|
||||||
|
Now we need to tell Railway about your custom domain.
|
||||||
|
|
||||||
|
### Step 8.1: Add Custom Domain in Railway
|
||||||
|
|
||||||
|
1. Go to **Railway dashboard**
|
||||||
|
2. Select your **backend** service
|
||||||
|
3. Go to **Settings** → **Networking**
|
||||||
|
4. Under **Custom Domains**, click **"Add Domain"**
|
||||||
|
5. Enter: `os.blackroad.systems`
|
||||||
|
6. Click **"Add"**
|
||||||
|
|
||||||
|
**Railway will:**
|
||||||
|
- Automatically provision an SSL certificate (via Let's Encrypt)
|
||||||
|
- Show a green checkmark when ready (usually takes 1-2 minutes)
|
||||||
|
|
||||||
|
### Step 8.2: Repeat for Other Subdomains
|
||||||
|
|
||||||
|
Add these custom domains to Railway:
|
||||||
|
- `api.blackroad.systems`
|
||||||
|
- `prism.blackroad.systems`
|
||||||
|
|
||||||
|
**Note:** Each subdomain that points to Railway needs to be added in Railway's custom domains.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 9: Test Everything
|
||||||
|
|
||||||
|
Let's verify everything is working!
|
||||||
|
|
||||||
|
### Step 9.1: Test HTTPS
|
||||||
|
|
||||||
|
Open these URLs in your browser (or on your iPhone):
|
||||||
|
|
||||||
|
1. https://blackroad.systems
|
||||||
|
2. https://www.blackroad.systems (should redirect to above)
|
||||||
|
3. https://os.blackroad.systems
|
||||||
|
4. https://api.blackroad.systems/health
|
||||||
|
5. https://docs.blackroad.systems
|
||||||
|
|
||||||
|
**Check for:**
|
||||||
|
- ✅ Green padlock in browser (🔒)
|
||||||
|
- ✅ Page loads correctly
|
||||||
|
- ✅ No certificate warnings
|
||||||
|
|
||||||
|
### Step 9.2: Test HTTP → HTTPS Redirect
|
||||||
|
|
||||||
|
Try loading without HTTPS:
|
||||||
|
|
||||||
|
1. http://blackroad.systems (should redirect to https://)
|
||||||
|
|
||||||
|
**Should automatically redirect** to HTTPS version.
|
||||||
|
|
||||||
|
### Step 9.3: Test DNS Propagation
|
||||||
|
|
||||||
|
Use these tools to verify DNS is working globally:
|
||||||
|
|
||||||
|
1. **DNS Checker**: https://dnschecker.org
|
||||||
|
- Enter: `blackroad.systems`
|
||||||
|
- Should show Cloudflare IPs
|
||||||
|
|
||||||
|
2. **Cloudflare DNS Lookup**: https://1.1.1.1/dns/
|
||||||
|
- Enter: `os.blackroad.systems`
|
||||||
|
- Should resolve correctly
|
||||||
|
|
||||||
|
### Step 9.4: Check Cloudflare Analytics
|
||||||
|
|
||||||
|
1. Go to Cloudflare → **Analytics & Logs** → **Traffic**
|
||||||
|
2. You should start seeing traffic data within a few minutes
|
||||||
|
3. This confirms traffic is flowing through Cloudflare
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 10: Repeat for Other Domains
|
||||||
|
|
||||||
|
Now that you've done `blackroad.systems`, repeat the same process for:
|
||||||
|
|
||||||
|
**Phase 1 Domains** (do these now):
|
||||||
|
- [ ] `blackroad.ai`
|
||||||
|
- [ ] `blackroad.network`
|
||||||
|
- [ ] `blackroad.me`
|
||||||
|
|
||||||
|
**Phase 2 Domains** (do these later):
|
||||||
|
- [ ] `lucidia.earth`
|
||||||
|
- [ ] `aliceqi.com`
|
||||||
|
- [ ] `blackroadqi.com`
|
||||||
|
- [ ] `roadwallet.com`
|
||||||
|
- [ ] `aliceos.io`
|
||||||
|
- [ ] `blackroadquantum.com`
|
||||||
|
|
||||||
|
**For each domain, follow the same steps:**
|
||||||
|
1. Part 2: Add domain to Cloudflare
|
||||||
|
2. Part 3: Update nameservers at GoDaddy
|
||||||
|
3. Part 4: Wait for "Active" status
|
||||||
|
4. Part 5: Configure DNS records
|
||||||
|
5. Part 6: Configure SSL/TLS
|
||||||
|
6. Part 7: Optimize performance
|
||||||
|
7. Part 8: Add custom domains to Railway (if needed)
|
||||||
|
8. Part 9: Test
|
||||||
|
|
||||||
|
**Pro tip:** You can start the process for multiple domains in parallel (add them all to Cloudflare and change nameservers), then configure them one at a time while DNS propagates.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### DNS Not Resolving
|
||||||
|
|
||||||
|
**Problem:** Domain doesn't load after changing nameservers
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. **Wait longer** - DNS can take up to 48 hours (usually 5-60 minutes)
|
||||||
|
2. **Clear your browser cache** - Hard refresh (Cmd+Shift+R on Mac, Ctrl+Shift+R on PC)
|
||||||
|
3. **Check nameservers** - Go to https://www.whatsmydns.net and enter your domain
|
||||||
|
4. **Verify at GoDaddy** - Make sure nameservers are saved correctly
|
||||||
|
|
||||||
|
### SSL Certificate Error
|
||||||
|
|
||||||
|
**Problem:** Browser shows "Not Secure" or certificate warning
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. **Check SSL/TLS mode** - Should be "Full (strict)" in Cloudflare
|
||||||
|
2. **Wait for Railway SSL** - Check Railway dashboard for green checkmark
|
||||||
|
3. **Check custom domain** - Make sure domain is added in Railway settings
|
||||||
|
4. **Try incognito mode** - Sometimes browser cache causes issues
|
||||||
|
|
||||||
|
### Site Not Loading
|
||||||
|
|
||||||
|
**Problem:** Domain resolves but site doesn't load (blank page, 502 error)
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. **Check Railway app** - Make sure backend is deployed and healthy
|
||||||
|
2. **Check Railway logs** - Look for errors: Railway dashboard → Logs
|
||||||
|
3. **Test Railway URL directly** - Try `your-app.up.railway.app` to isolate issue
|
||||||
|
4. **Check DNS records** - Make sure CNAME points to correct Railway URL
|
||||||
|
|
||||||
|
### Mixed Content Warnings
|
||||||
|
|
||||||
|
**Problem:** Page loads but some assets show as insecure (broken padlock)
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. **Enable "Automatic HTTPS Rewrites"** - In Cloudflare SSL/TLS settings
|
||||||
|
2. **Check your code** - Make sure no hard-coded `http://` URLs
|
||||||
|
3. **Use relative URLs** - In your HTML/JS, use `/api/...` instead of full URLs
|
||||||
|
|
||||||
|
### Email Stopped Working
|
||||||
|
|
||||||
|
**Problem:** Can't send/receive emails after DNS migration
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
1. **Check MX records** - Make sure they're configured in Cloudflare DNS
|
||||||
|
2. **MX records must be DNS-only** - Turn OFF proxy (grey cloud) for MX records
|
||||||
|
3. **Verify SPF/DKIM** - Make sure TXT records for email are present
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getting Help
|
||||||
|
|
||||||
|
If you run into issues:
|
||||||
|
|
||||||
|
1. **Cloudflare Community**: https://community.cloudflare.com
|
||||||
|
2. **Railway Discord**: https://discord.gg/railway
|
||||||
|
3. **Check Cloudflare Status**: https://www.cloudflarestatus.com
|
||||||
|
4. **Check Railway Status**: https://status.railway.app
|
||||||
|
|
||||||
|
**For BlackRoad-specific issues:**
|
||||||
|
- Open an issue in the repo
|
||||||
|
- Check `CLAUDE.md` for developer docs
|
||||||
|
- Review `MASTER_ORCHESTRATION_PLAN.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
After DNS migration is complete:
|
||||||
|
|
||||||
|
- [ ] Set up **Page Rules** for WWW redirects (see Part 11 below)
|
||||||
|
- [ ] Enable **Web Analytics** in Cloudflare
|
||||||
|
- [ ] Set up **Firewall Rules** (optional)
|
||||||
|
- [ ] Configure **Workers** for edge functions (Phase 2)
|
||||||
|
- [ ] Set up **Cloudflare Access** for zero-trust security (Phase 2)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 11: Set Up Page Rules (Optional but Recommended)
|
||||||
|
|
||||||
|
Page Rules let you configure special behaviors for specific URLs.
|
||||||
|
|
||||||
|
### Step 11.1: Create WWW Redirect Rule
|
||||||
|
|
||||||
|
1. Go to Cloudflare → **Rules** → **Page Rules**
|
||||||
|
2. Click **"Create Page Rule"**
|
||||||
|
3. Enter URL: `www.blackroad.systems/*`
|
||||||
|
4. Click **"Add a Setting"** → **"Forwarding URL"**
|
||||||
|
5. Select **"301 - Permanent Redirect"**
|
||||||
|
6. Enter destination: `https://blackroad.systems/$1`
|
||||||
|
7. Click **"Save and Deploy"**
|
||||||
|
|
||||||
|
**What this does:** Redirects www.blackroad.systems to blackroad.systems (keeps the path)
|
||||||
|
|
||||||
|
### Step 11.2: Bypass API Caching
|
||||||
|
|
||||||
|
Create a rule to prevent API responses from being cached:
|
||||||
|
|
||||||
|
1. Click **"Create Page Rule"**
|
||||||
|
2. Enter URL: `*blackroad.systems/api/*`
|
||||||
|
3. Add settings:
|
||||||
|
- **Cache Level** → Bypass
|
||||||
|
- **Disable Performance** (optional)
|
||||||
|
4. Click **"Save and Deploy"**
|
||||||
|
|
||||||
|
**What this does:** Ensures API calls always hit your backend (no stale cached data)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Checklist: Migration Complete
|
||||||
|
|
||||||
|
Mark these when done:
|
||||||
|
|
||||||
|
- [ ] Domain added to Cloudflare
|
||||||
|
- [ ] Nameservers updated at GoDaddy
|
||||||
|
- [ ] DNS status shows "Active" in Cloudflare
|
||||||
|
- [ ] DNS records configured (from records.yaml)
|
||||||
|
- [ ] SSL/TLS set to "Full (strict)"
|
||||||
|
- [ ] "Always Use HTTPS" enabled
|
||||||
|
- [ ] Auto Minify enabled
|
||||||
|
- [ ] Brotli compression enabled
|
||||||
|
- [ ] Custom domains added to Railway
|
||||||
|
- [ ] HTTPS works (green padlock 🔒)
|
||||||
|
- [ ] HTTP → HTTPS redirect works
|
||||||
|
- [ ] WWW → apex redirect works
|
||||||
|
- [ ] API endpoint responding
|
||||||
|
- [ ] Docs subdomain works
|
||||||
|
- [ ] No console errors in browser
|
||||||
|
- [ ] Cloudflare Analytics showing traffic
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Success! 🎉
|
||||||
|
|
||||||
|
You've successfully migrated DNS to Cloudflare! Your domains now benefit from:
|
||||||
|
|
||||||
|
- ✅ Free SSL certificates
|
||||||
|
- ✅ CDN (faster loading worldwide)
|
||||||
|
- ✅ DDoS protection
|
||||||
|
- ✅ Better security
|
||||||
|
- ✅ Advanced features ready for Phase 2
|
||||||
|
|
||||||
|
**What's next?**
|
||||||
|
- Move on to Phase 1 infrastructure tasks
|
||||||
|
- Set up GitHub Actions secrets
|
||||||
|
- Configure Railway environment variables
|
||||||
|
- Deploy your first updates through the new infrastructure
|
||||||
|
|
||||||
|
**Where AI meets the open road.** 🛣️
|
||||||
426
infra/cloudflare/records.yaml
Normal file
426
infra/cloudflare/records.yaml
Normal file
@@ -0,0 +1,426 @@
|
|||||||
|
# Cloudflare DNS Records Configuration
|
||||||
|
# Version: 1.0
|
||||||
|
# Purpose: Structured DNS configuration for all BlackRoad domains
|
||||||
|
#
|
||||||
|
# This file is the single source of truth for DNS records.
|
||||||
|
# Use cloudflare_dns_sync.py to apply these records to Cloudflare.
|
||||||
|
#
|
||||||
|
# Format:
|
||||||
|
# domain: Domain name being managed
|
||||||
|
# zone_id: Cloudflare zone ID (get from Cloudflare dashboard)
|
||||||
|
# records: List of DNS records for this domain
|
||||||
|
# - type: Record type (A, AAAA, CNAME, TXT, MX, etc.)
|
||||||
|
# name: Record name (@ for root, subdomain for others)
|
||||||
|
# content: Target value
|
||||||
|
# ttl: Time to live (1 = Auto)
|
||||||
|
# proxied: Whether to proxy through Cloudflare (true/false)
|
||||||
|
# priority: MX priority (for MX records only)
|
||||||
|
# comment: Human-readable description
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# PHASE 1 DOMAINS (Active Now)
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
- domain: blackroad.systems
|
||||||
|
zone_id: REPLACE_WITH_ZONE_ID_FROM_CLOUDFLARE
|
||||||
|
primary: true
|
||||||
|
phase: 1
|
||||||
|
records:
|
||||||
|
# Root domain → Railway backend
|
||||||
|
- type: CNAME
|
||||||
|
name: '@'
|
||||||
|
content: blackroad-os-production.up.railway.app
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: Root domain points to Railway production (CNAME flattening)
|
||||||
|
|
||||||
|
# WWW redirect
|
||||||
|
- type: CNAME
|
||||||
|
name: www
|
||||||
|
content: blackroad.systems
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: WWW subdomain redirects to apex
|
||||||
|
|
||||||
|
# OS subdomain (explicit alias)
|
||||||
|
- type: CNAME
|
||||||
|
name: os
|
||||||
|
content: blackroad.systems
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: Explicit OS subdomain (alternative entry point)
|
||||||
|
|
||||||
|
# API subdomain
|
||||||
|
- type: CNAME
|
||||||
|
name: api
|
||||||
|
content: blackroad-os-production.up.railway.app
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: Explicit API subdomain for developers
|
||||||
|
|
||||||
|
# Prism Console subdomain
|
||||||
|
- type: CNAME
|
||||||
|
name: prism
|
||||||
|
content: blackroad-os-production.up.railway.app
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: Prism Console admin interface
|
||||||
|
|
||||||
|
# Documentation subdomain → GitHub Pages
|
||||||
|
- type: CNAME
|
||||||
|
name: docs
|
||||||
|
content: blackboxprogramming.github.io
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: Documentation hosted on GitHub Pages
|
||||||
|
|
||||||
|
# CDN subdomain (for future asset delivery)
|
||||||
|
- type: CNAME
|
||||||
|
name: cdn
|
||||||
|
content: blackroad.systems
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: CDN alias for static assets
|
||||||
|
|
||||||
|
# Email records (Google Workspace configuration)
|
||||||
|
- type: TXT
|
||||||
|
name: '@'
|
||||||
|
content: v=spf1 include:_spf.google.com ~all
|
||||||
|
ttl: 1
|
||||||
|
proxied: false
|
||||||
|
comment: SPF record for Google Workspace email
|
||||||
|
|
||||||
|
- type: MX
|
||||||
|
name: '@'
|
||||||
|
content: aspmx.l.google.com
|
||||||
|
ttl: 1
|
||||||
|
proxied: false
|
||||||
|
priority: 1
|
||||||
|
comment: Google Workspace MX record (priority 1)
|
||||||
|
|
||||||
|
- type: MX
|
||||||
|
name: '@'
|
||||||
|
content: alt1.aspmx.l.google.com
|
||||||
|
ttl: 1
|
||||||
|
proxied: false
|
||||||
|
priority: 5
|
||||||
|
comment: Google Workspace MX record (priority 5)
|
||||||
|
|
||||||
|
- type: MX
|
||||||
|
name: '@'
|
||||||
|
content: alt2.aspmx.l.google.com
|
||||||
|
ttl: 1
|
||||||
|
proxied: false
|
||||||
|
priority: 5
|
||||||
|
comment: Google Workspace MX record (priority 5)
|
||||||
|
|
||||||
|
# Verification records (add as needed)
|
||||||
|
# - type: TXT
|
||||||
|
# name: '@'
|
||||||
|
# content: google-site-verification=YOUR_CODE_HERE
|
||||||
|
# ttl: 1
|
||||||
|
# proxied: false
|
||||||
|
# comment: Google Search Console verification
|
||||||
|
|
||||||
|
- domain: blackroad.ai
|
||||||
|
zone_id: REPLACE_WITH_ZONE_ID_FROM_CLOUDFLARE
|
||||||
|
primary: false
|
||||||
|
phase: 1
|
||||||
|
records:
|
||||||
|
# Root → Alias to main OS
|
||||||
|
- type: CNAME
|
||||||
|
name: '@'
|
||||||
|
content: os.blackroad.systems
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: Product console entry point
|
||||||
|
|
||||||
|
# WWW redirect
|
||||||
|
- type: CNAME
|
||||||
|
name: www
|
||||||
|
content: blackroad.ai
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: WWW subdomain redirects to apex
|
||||||
|
|
||||||
|
# Console subdomain (explicit)
|
||||||
|
- type: CNAME
|
||||||
|
name: console
|
||||||
|
content: os.blackroad.systems
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: Explicit console subdomain
|
||||||
|
|
||||||
|
- domain: blackroad.network
|
||||||
|
zone_id: REPLACE_WITH_ZONE_ID_FROM_CLOUDFLARE
|
||||||
|
primary: false
|
||||||
|
phase: 1
|
||||||
|
records:
|
||||||
|
# Root → GitHub Pages (developer docs)
|
||||||
|
- type: CNAME
|
||||||
|
name: '@'
|
||||||
|
content: blackboxprogramming.github.io
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: Developer hub and documentation
|
||||||
|
|
||||||
|
# WWW redirect
|
||||||
|
- type: CNAME
|
||||||
|
name: www
|
||||||
|
content: blackroad.network
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: WWW subdomain redirects to apex
|
||||||
|
|
||||||
|
# API subdomain (for developer API access)
|
||||||
|
- type: CNAME
|
||||||
|
name: api
|
||||||
|
content: blackroad-os-production.up.railway.app
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: API access for developers
|
||||||
|
|
||||||
|
# Sandbox subdomain → Railway staging
|
||||||
|
- type: CNAME
|
||||||
|
name: sandbox
|
||||||
|
content: blackroad-os-staging.up.railway.app
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: Staging/sandbox environment for testing
|
||||||
|
|
||||||
|
- domain: blackroad.me
|
||||||
|
zone_id: REPLACE_WITH_ZONE_ID_FROM_CLOUDFLARE
|
||||||
|
primary: false
|
||||||
|
phase: 1
|
||||||
|
records:
|
||||||
|
# Root → Identity portal (via main OS)
|
||||||
|
- type: CNAME
|
||||||
|
name: '@'
|
||||||
|
content: os.blackroad.systems
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: Personal identity portal
|
||||||
|
|
||||||
|
# WWW redirect
|
||||||
|
- type: CNAME
|
||||||
|
name: www
|
||||||
|
content: blackroad.me
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: WWW subdomain redirects to apex
|
||||||
|
|
||||||
|
# ID subdomain (explicit identity)
|
||||||
|
- type: CNAME
|
||||||
|
name: id
|
||||||
|
content: os.blackroad.systems
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: Explicit identity subdomain
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# PHASE 2 DOMAINS (Launch Later)
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
- domain: lucidia.earth
|
||||||
|
zone_id: REPLACE_WITH_ZONE_ID_FROM_CLOUDFLARE
|
||||||
|
primary: false
|
||||||
|
phase: 2
|
||||||
|
records:
|
||||||
|
# Root → GitHub Pages (narrative site)
|
||||||
|
- type: CNAME
|
||||||
|
name: '@'
|
||||||
|
content: blackboxprogramming.github.io
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: Narrative experiences and interactive storytelling
|
||||||
|
|
||||||
|
# WWW redirect
|
||||||
|
- type: CNAME
|
||||||
|
name: www
|
||||||
|
content: lucidia.earth
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: WWW subdomain redirects to apex
|
||||||
|
|
||||||
|
# Studio subdomain (Phase 3 - creative tools)
|
||||||
|
- type: CNAME
|
||||||
|
name: studio
|
||||||
|
content: lucidia-studio.vercel.app
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: Lucidia Studio creative production platform
|
||||||
|
|
||||||
|
- domain: aliceqi.com
|
||||||
|
zone_id: REPLACE_WITH_ZONE_ID_FROM_CLOUDFLARE
|
||||||
|
primary: false
|
||||||
|
phase: 2
|
||||||
|
records:
|
||||||
|
# Root → GitHub Pages (research showcase)
|
||||||
|
- type: CNAME
|
||||||
|
name: '@'
|
||||||
|
content: blackboxprogramming.github.io
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: ALICE QI research showcase
|
||||||
|
|
||||||
|
# WWW redirect
|
||||||
|
- type: CNAME
|
||||||
|
name: www
|
||||||
|
content: aliceqi.com
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: WWW subdomain redirects to apex
|
||||||
|
|
||||||
|
# Research subdomain
|
||||||
|
- type: CNAME
|
||||||
|
name: research
|
||||||
|
content: aliceqi.com
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: Research portal
|
||||||
|
|
||||||
|
# Docs subdomain
|
||||||
|
- type: CNAME
|
||||||
|
name: docs
|
||||||
|
content: aliceqi.com
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: Technical documentation
|
||||||
|
|
||||||
|
- domain: blackroadqi.com
|
||||||
|
zone_id: REPLACE_WITH_ZONE_ID_FROM_CLOUDFLARE
|
||||||
|
primary: false
|
||||||
|
phase: 2
|
||||||
|
records:
|
||||||
|
# Root → Dedicated QI app on Railway
|
||||||
|
- type: CNAME
|
||||||
|
name: '@'
|
||||||
|
content: blackroadqi-app.up.railway.app
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: Financial/quantitative intelligence product
|
||||||
|
|
||||||
|
# WWW redirect
|
||||||
|
- type: CNAME
|
||||||
|
name: www
|
||||||
|
content: blackroadqi.com
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: WWW subdomain redirects to apex
|
||||||
|
|
||||||
|
# API subdomain
|
||||||
|
- type: CNAME
|
||||||
|
name: api
|
||||||
|
content: blackroadqi-api.up.railway.app
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: QI API endpoint
|
||||||
|
|
||||||
|
- domain: roadwallet.com
|
||||||
|
zone_id: REPLACE_WITH_ZONE_ID_FROM_CLOUDFLARE
|
||||||
|
primary: false
|
||||||
|
phase: 2
|
||||||
|
records:
|
||||||
|
# Root → Alias to main OS wallet interface
|
||||||
|
- type: CNAME
|
||||||
|
name: '@'
|
||||||
|
content: os.blackroad.systems
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: Wallet interface via main OS
|
||||||
|
|
||||||
|
# WWW redirect
|
||||||
|
- type: CNAME
|
||||||
|
name: www
|
||||||
|
content: roadwallet.com
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: WWW subdomain redirects to apex
|
||||||
|
|
||||||
|
- domain: aliceos.io
|
||||||
|
zone_id: REPLACE_WITH_ZONE_ID_FROM_CLOUDFLARE
|
||||||
|
primary: false
|
||||||
|
phase: 2
|
||||||
|
records:
|
||||||
|
# Root → Legacy alias to main OS
|
||||||
|
- type: CNAME
|
||||||
|
name: '@'
|
||||||
|
content: os.blackroad.systems
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: Legacy domain alias
|
||||||
|
|
||||||
|
# WWW redirect
|
||||||
|
- type: CNAME
|
||||||
|
name: www
|
||||||
|
content: aliceos.io
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: WWW subdomain redirects to apex
|
||||||
|
|
||||||
|
- domain: blackroadquantum.com
|
||||||
|
zone_id: REPLACE_WITH_ZONE_ID_FROM_CLOUDFLARE
|
||||||
|
primary: false
|
||||||
|
phase: 2
|
||||||
|
records:
|
||||||
|
# Root → GitHub Pages (research hub)
|
||||||
|
- type: CNAME
|
||||||
|
name: '@'
|
||||||
|
content: blackboxprogramming.github.io
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: Quantum research hub
|
||||||
|
|
||||||
|
# WWW redirect
|
||||||
|
- type: CNAME
|
||||||
|
name: www
|
||||||
|
content: blackroadquantum.com
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: WWW subdomain redirects to apex
|
||||||
|
|
||||||
|
# Lab subdomain → Dedicated Quantum Lab app
|
||||||
|
- type: CNAME
|
||||||
|
name: lab
|
||||||
|
content: quantum-lab.up.railway.app
|
||||||
|
ttl: 1
|
||||||
|
proxied: true
|
||||||
|
comment: Quantum Lab application
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# NOTES
|
||||||
|
# ============================================================================
|
||||||
|
#
|
||||||
|
# CNAME Flattening:
|
||||||
|
# Cloudflare allows CNAME records at the root (@) level via CNAME flattening.
|
||||||
|
# This is not standard DNS but works with Cloudflare proxy enabled.
|
||||||
|
#
|
||||||
|
# Railway Targets:
|
||||||
|
# Replace 'blackroad-os-production.up.railway.app' with your actual Railway
|
||||||
|
# app URL from Railway dashboard → Service → Settings → Domains.
|
||||||
|
#
|
||||||
|
# Zone IDs:
|
||||||
|
# Get zone IDs from Cloudflare dashboard → Domain → Overview (right sidebar).
|
||||||
|
# Replace all 'REPLACE_WITH_ZONE_ID_FROM_CLOUDFLARE' with actual zone IDs.
|
||||||
|
#
|
||||||
|
# GitHub Pages CNAME:
|
||||||
|
# For GitHub Pages domains, you must also create a CNAME file in your
|
||||||
|
# GitHub Pages repo root containing the custom domain:
|
||||||
|
# echo "blackroad.network" > CNAME
|
||||||
|
#
|
||||||
|
# Email Configuration:
|
||||||
|
# The MX/SPF records are examples for Google Workspace. Update or remove
|
||||||
|
# if using a different email provider.
|
||||||
|
#
|
||||||
|
# SSL/TLS:
|
||||||
|
# Set Cloudflare SSL/TLS mode to "Full (strict)" for all domains.
|
||||||
|
# Railway automatically provides SSL certificates.
|
||||||
|
#
|
||||||
|
# Proxied vs DNS-only:
|
||||||
|
# - proxied: true → Traffic goes through Cloudflare (CDN, DDoS protection)
|
||||||
|
# - proxied: false → Direct DNS resolution (required for MX, some TXT records)
|
||||||
|
#
|
||||||
|
# Automation:
|
||||||
|
# Use cloudflare_dns_sync.py to apply these records automatically via API.
|
||||||
699
infra/frontend/LANDING_PAGE_PLAN.md
Normal file
699
infra/frontend/LANDING_PAGE_PLAN.md
Normal file
@@ -0,0 +1,699 @@
|
|||||||
|
# BlackRoad Landing Page Implementation Plan
|
||||||
|
|
||||||
|
**Version:** 1.0
|
||||||
|
**Date:** 2025-11-18
|
||||||
|
**Purpose:** Detailed plan for implementing blackroad.systems landing page
|
||||||
|
**Target:** Phase 1 - Production MVP
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Create a professional, conversion-focused landing page for `blackroad.systems` that:
|
||||||
|
- Positions BlackRoad OS as the future of AI orchestration
|
||||||
|
- Drives design partner signups
|
||||||
|
- Provides clear value proposition
|
||||||
|
- Showcases key capabilities
|
||||||
|
- Links to documentation and product
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Site Structure
|
||||||
|
|
||||||
|
### Pages (Phase 1 MVP)
|
||||||
|
|
||||||
|
1. **Homepage** (`/`) - Hero, value prop, CTA
|
||||||
|
2. **Architecture** (`/architecture`) - Technical overview
|
||||||
|
3. **Solutions** (`/solutions/financial-services`) - First industry vertical
|
||||||
|
4. **Pricing** (`/pricing`) - 3-tier model
|
||||||
|
5. **Contact** (`/contact`) - Demo request form
|
||||||
|
|
||||||
|
**Total:** 5 pages minimum for Phase 1
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Page 1: Homepage
|
||||||
|
|
||||||
|
### Hero Section
|
||||||
|
**Visual:** Full-width hero with animated gradient background (Windows 95 aesthetic meets modern design)
|
||||||
|
|
||||||
|
**Headline:**
|
||||||
|
```
|
||||||
|
Where AI Meets the Open Road
|
||||||
|
```
|
||||||
|
|
||||||
|
**Subheadline:**
|
||||||
|
```
|
||||||
|
The operating system for autonomous AI orchestration.
|
||||||
|
200+ agents. Zero black boxes. Complete auditability.
|
||||||
|
```
|
||||||
|
|
||||||
|
**CTA Buttons:**
|
||||||
|
- Primary: "Request Design Partner Access" (links to /contact)
|
||||||
|
- Secondary: "View Architecture" (links to /architecture)
|
||||||
|
- Tertiary: "Explore Docs" (links to blackroad.network)
|
||||||
|
|
||||||
|
**Background Elements:**
|
||||||
|
- Subtle animated grid (like Windows 95 desktop pattern)
|
||||||
|
- Floating "windows" with code snippets
|
||||||
|
- Smooth gradient (teal → purple)
|
||||||
|
|
||||||
|
### Capabilities Section
|
||||||
|
**Headline:** "Built for the Future of AI Work"
|
||||||
|
|
||||||
|
**Three Columns:**
|
||||||
|
|
||||||
|
1. **Multi-Agent Orchestration**
|
||||||
|
- Icon: 🤖 (or custom agent icon)
|
||||||
|
- Description: "200+ autonomous agents working together. From code reviews to compliance audits, orchestrated intelligently."
|
||||||
|
- Link: "Learn more →"
|
||||||
|
|
||||||
|
2. **Provable & Auditable**
|
||||||
|
- Icon: 🔒 (or blockchain icon)
|
||||||
|
- Description: "Every action logged on RoadChain. Full tamper-evident audit trails for compliance and governance."
|
||||||
|
- Link: "See architecture →"
|
||||||
|
|
||||||
|
3. **Human-in-the-Loop**
|
||||||
|
- Icon: 👤 (or human + AI icon)
|
||||||
|
- Description: "Humans orchestrate, agents execute. Approval gates, review steps, and full transparency."
|
||||||
|
- Link: "View workflow →"
|
||||||
|
|
||||||
|
### Use Cases Section
|
||||||
|
**Headline:** "Powering AI-First Organizations"
|
||||||
|
|
||||||
|
**Three Cards (with hover effects):**
|
||||||
|
|
||||||
|
1. **Financial Services**
|
||||||
|
- "Deploy 500 trading agents with complete regulatory compliance"
|
||||||
|
- CTA: "Read case study →"
|
||||||
|
|
||||||
|
2. **Healthcare**
|
||||||
|
- "Ensure HIPAA compliance across all AI operations"
|
||||||
|
- CTA: "Learn how →"
|
||||||
|
|
||||||
|
3. **Enterprise**
|
||||||
|
- "Replace black-box AI with deterministic, auditable intelligence"
|
||||||
|
- CTA: "See enterprise features →"
|
||||||
|
|
||||||
|
### Social Proof (Placeholder for Now)
|
||||||
|
**Headline:** "Trusted by Forward-Thinking Organizations"
|
||||||
|
|
||||||
|
**Logos:** (Placeholder boxes with "Design Partner" text)
|
||||||
|
- 5 placeholder boxes
|
||||||
|
- Text: "Join our design partner program →"
|
||||||
|
|
||||||
|
### Tech Stack Section
|
||||||
|
**Headline:** "Built on Modern Infrastructure"
|
||||||
|
|
||||||
|
**Badges/Icons:**
|
||||||
|
- Python / FastAPI
|
||||||
|
- PostgreSQL
|
||||||
|
- Redis
|
||||||
|
- Railway
|
||||||
|
- Cloudflare
|
||||||
|
- Blockchain (RoadChain)
|
||||||
|
- ALICE QI (AI engine)
|
||||||
|
|
||||||
|
### Final CTA
|
||||||
|
**Large, centered section with gradient background**
|
||||||
|
|
||||||
|
**Headline:** "Ready to Orchestrate AI Without Limits?"
|
||||||
|
|
||||||
|
**Buttons:**
|
||||||
|
- Primary: "Request Design Partner Access"
|
||||||
|
- Secondary: "Schedule a Demo"
|
||||||
|
|
||||||
|
**Footer Note:**
|
||||||
|
"Available for select design partners in 2025. Early access includes dedicated support and custom integrations."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Page 2: Architecture
|
||||||
|
|
||||||
|
### Header
|
||||||
|
**Headline:** "The BlackRoad OS Architecture"
|
||||||
|
**Subheadline:** "Seven layers of deterministic, auditable AI orchestration"
|
||||||
|
|
||||||
|
### The Stack Diagram
|
||||||
|
Visual representation of the 7 layers (vertical stack):
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ 7. USER EXPERIENCE │
|
||||||
|
│ blackroad.systems • blackroad.ai │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
↕
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ 6. APPLICATION LAYER (Pocket OS) │
|
||||||
|
│ Native Apps • Win95 UI • WebSocket │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
↕
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ 5. API GATEWAY & ROUTING │
|
||||||
|
│ FastAPI • REST • GraphQL │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
↕
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ 4. ORCHESTRATION & INTELLIGENCE │
|
||||||
|
│ Lucidia • Prism • Operator Engine │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
↕
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ 3. DATA & STATE │
|
||||||
|
│ Postgres • Redis • RoadChain • Vault │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
↕
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ 2. COMPUTE & INFRASTRUCTURE │
|
||||||
|
│ Railway • CloudWay • Edge Functions │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
↕
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ 1. DNS & CDN │
|
||||||
|
│ Cloudflare • Global Distribution │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Core Components
|
||||||
|
|
||||||
|
**Expandable accordions for each layer:**
|
||||||
|
|
||||||
|
1. **Lucidia Layer**
|
||||||
|
- Multi-model AI orchestration
|
||||||
|
- Claude, GPT, Llama integration
|
||||||
|
- Long-term memory
|
||||||
|
- Tool calling
|
||||||
|
|
||||||
|
2. **Prism Layer**
|
||||||
|
- Job queue & event log
|
||||||
|
- Metrics & monitoring
|
||||||
|
- Backpressure control
|
||||||
|
- Scheduler
|
||||||
|
|
||||||
|
3. **RoadChain**
|
||||||
|
- Tamper-evident audit trail
|
||||||
|
- Cryptographic provenance
|
||||||
|
- Compliance-ready
|
||||||
|
- Immutable logs
|
||||||
|
|
||||||
|
4. **PS-SHA∞ Identity**
|
||||||
|
- Sovereign identity for agents
|
||||||
|
- Recursive self-description
|
||||||
|
- Unique agent signatures
|
||||||
|
|
||||||
|
### Technical Specs
|
||||||
|
**Table format:**
|
||||||
|
|
||||||
|
| Component | Technology | Purpose |
|
||||||
|
|-----------|-----------|---------|
|
||||||
|
| Backend | FastAPI (Python) | Async API server |
|
||||||
|
| Database | PostgreSQL 15 | Relational data |
|
||||||
|
| Cache | Redis 7 | Sessions, pub/sub |
|
||||||
|
| Frontend | Vanilla JS | Zero-dependency UI |
|
||||||
|
| Deployment | Railway | Cloud platform |
|
||||||
|
| CDN | Cloudflare | Global edge network |
|
||||||
|
| Blockchain | RoadChain | Audit ledger |
|
||||||
|
|
||||||
|
### CTA
|
||||||
|
"Dive deeper into our technical documentation →"
|
||||||
|
Link to: blackroad.network
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Page 3: Solutions - Financial Services
|
||||||
|
|
||||||
|
### Hero
|
||||||
|
**Headline:** "AI Orchestration for Financial Services"
|
||||||
|
**Subheadline:** "Deploy trading agents, compliance bots, and risk analysis—all with complete auditability."
|
||||||
|
|
||||||
|
### The Challenge
|
||||||
|
**Section:** "The Problem with Black-Box AI"
|
||||||
|
|
||||||
|
**Pain points:**
|
||||||
|
- ❌ No audit trail for compliance
|
||||||
|
- ❌ Can't explain model decisions to regulators
|
||||||
|
- ❌ Single-model risk (vendor lock-in)
|
||||||
|
- ❌ No human oversight in critical decisions
|
||||||
|
|
||||||
|
### The BlackRoad Solution
|
||||||
|
**Section:** "How BlackRoad Solves It"
|
||||||
|
|
||||||
|
**Benefits:**
|
||||||
|
- ✅ Complete audit trail via RoadChain
|
||||||
|
- ✅ Multi-model orchestration (no vendor lock-in)
|
||||||
|
- ✅ Human-in-the-loop approval gates
|
||||||
|
- ✅ Deterministic, reproducible results
|
||||||
|
|
||||||
|
### Use Cases
|
||||||
|
**Three cards:**
|
||||||
|
|
||||||
|
1. **Trading Agents**
|
||||||
|
- Deploy 500 autonomous trading agents
|
||||||
|
- Full regulatory compliance (MiFID II, Dodd-Frank)
|
||||||
|
- Real-time risk monitoring
|
||||||
|
- Instant rollback on market anomalies
|
||||||
|
|
||||||
|
2. **Compliance Automation**
|
||||||
|
- Automated KYC/AML checks
|
||||||
|
- Transaction monitoring
|
||||||
|
- Suspicious activity reports
|
||||||
|
- Regulatory filing automation
|
||||||
|
|
||||||
|
3. **Risk Analysis**
|
||||||
|
- Portfolio risk assessment
|
||||||
|
- Stress testing
|
||||||
|
- Scenario analysis
|
||||||
|
- Real-time dashboards
|
||||||
|
|
||||||
|
### Case Study (Placeholder)
|
||||||
|
**Headline:** "How [Bank Name] Deployed 500 AI Agents with Zero Compliance Issues"
|
||||||
|
|
||||||
|
**Stats:**
|
||||||
|
- 90% reduction in manual compliance work
|
||||||
|
- 100% audit trail coverage
|
||||||
|
- 24/7 automated monitoring
|
||||||
|
- Zero regulatory findings
|
||||||
|
|
||||||
|
**Quote:**
|
||||||
|
"BlackRoad gave us the confidence to deploy AI at scale without sacrificing regulatory compliance."
|
||||||
|
— [CTO, Major Bank]
|
||||||
|
|
||||||
|
### CTA
|
||||||
|
"Ready to transform your financial operations?"
|
||||||
|
Button: "Request a Custom Demo"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Page 4: Pricing
|
||||||
|
|
||||||
|
### Hero
|
||||||
|
**Headline:** "Simple, Transparent Pricing"
|
||||||
|
**Subheadline:** "Choose the plan that fits your organization"
|
||||||
|
|
||||||
|
### Pricing Tiers (3 columns)
|
||||||
|
|
||||||
|
#### 1. **Developer** (Free)
|
||||||
|
**Price:** $0/month
|
||||||
|
**For:** Individual developers, open source projects
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- ✅ Up to 10 agents
|
||||||
|
- ✅ 1 GB storage
|
||||||
|
- ✅ Community support
|
||||||
|
- ✅ Public documentation access
|
||||||
|
- ✅ Basic API access
|
||||||
|
- ❌ No SLA
|
||||||
|
- ❌ No custom integrations
|
||||||
|
|
||||||
|
**CTA:** "Start Building" (link to docs)
|
||||||
|
|
||||||
|
#### 2. **Team** ($499/month)
|
||||||
|
**Price:** $499/month
|
||||||
|
**For:** Small teams, startups
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- ✅ Up to 100 agents
|
||||||
|
- ✅ 50 GB storage
|
||||||
|
- ✅ Email support (48h response)
|
||||||
|
- ✅ Advanced monitoring
|
||||||
|
- ✅ Multi-user access
|
||||||
|
- ✅ Private repositories
|
||||||
|
- ✅ 99.9% SLA
|
||||||
|
|
||||||
|
**CTA:** "Start Free Trial" (14-day trial)
|
||||||
|
|
||||||
|
#### 3. **Enterprise** (Custom)
|
||||||
|
**Price:** Custom pricing
|
||||||
|
**For:** Large organizations, design partners
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- ✅ Unlimited agents
|
||||||
|
- ✅ Unlimited storage
|
||||||
|
- ✅ Dedicated support (24/7)
|
||||||
|
- ✅ Custom integrations
|
||||||
|
- ✅ On-premise deployment option
|
||||||
|
- ✅ Custom SLA (up to 99.99%)
|
||||||
|
- ✅ White-label options
|
||||||
|
- ✅ Dedicated account manager
|
||||||
|
- ✅ Custom training
|
||||||
|
|
||||||
|
**CTA:** "Contact Sales"
|
||||||
|
|
||||||
|
### Add-Ons
|
||||||
|
**Optional services:**
|
||||||
|
- Professional services: $200/hour
|
||||||
|
- Custom agent development: Custom quote
|
||||||
|
- Training workshops: $5,000/day
|
||||||
|
- Managed services: Starting at $2,000/month
|
||||||
|
|
||||||
|
### FAQ Section
|
||||||
|
**Common questions:**
|
||||||
|
|
||||||
|
**Q: What payment methods do you accept?**
|
||||||
|
A: Credit card, ACH, wire transfer, and purchase orders for Enterprise customers.
|
||||||
|
|
||||||
|
**Q: Can I upgrade/downgrade anytime?**
|
||||||
|
A: Yes! Plans are flexible and can be changed at any time.
|
||||||
|
|
||||||
|
**Q: What's included in support?**
|
||||||
|
A: Team tier gets email support (48h). Enterprise gets dedicated Slack channel + 24/7 on-call.
|
||||||
|
|
||||||
|
**Q: Is there a discount for annual billing?**
|
||||||
|
A: Yes! Get 2 months free when you pay annually.
|
||||||
|
|
||||||
|
**Q: Do you offer academic/nonprofit pricing?**
|
||||||
|
A: Yes! Contact us for special pricing.
|
||||||
|
|
||||||
|
### CTA
|
||||||
|
"Not sure which plan is right? Let's talk."
|
||||||
|
Button: "Schedule a Consultation"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Page 5: Contact / Demo Request
|
||||||
|
|
||||||
|
### Hero
|
||||||
|
**Headline:** "Let's Build the Future Together"
|
||||||
|
**Subheadline:** "Join our design partner program and shape the future of AI orchestration"
|
||||||
|
|
||||||
|
### Form (Left Side)
|
||||||
|
**Fields:**
|
||||||
|
|
||||||
|
1. **Your Name** (required)
|
||||||
|
2. **Email Address** (required)
|
||||||
|
3. **Company Name** (required)
|
||||||
|
4. **Job Title** (optional)
|
||||||
|
5. **Company Size** (dropdown)
|
||||||
|
- 1-10 employees
|
||||||
|
- 11-50 employees
|
||||||
|
- 51-200 employees
|
||||||
|
- 201-1,000 employees
|
||||||
|
- 1,000+ employees
|
||||||
|
6. **Industry** (dropdown)
|
||||||
|
- Financial Services
|
||||||
|
- Healthcare
|
||||||
|
- Technology
|
||||||
|
- Manufacturing
|
||||||
|
- Other
|
||||||
|
7. **What are you interested in?** (checkboxes)
|
||||||
|
- [ ] Design partner program
|
||||||
|
- [ ] Enterprise pilot
|
||||||
|
- [ ] Technical demo
|
||||||
|
- [ ] Custom integration
|
||||||
|
- [ ] Just exploring
|
||||||
|
8. **Tell us about your use case** (textarea, optional)
|
||||||
|
9. **Preferred contact method** (radio)
|
||||||
|
- ( ) Email
|
||||||
|
- ( ) Phone call
|
||||||
|
- ( ) Video meeting
|
||||||
|
|
||||||
|
**Submit Button:** "Request Access"
|
||||||
|
|
||||||
|
**Privacy note:**
|
||||||
|
"We respect your privacy. Your information will only be used to respond to your inquiry. See our Privacy Policy."
|
||||||
|
|
||||||
|
### Info (Right Side)
|
||||||
|
**Contact Information:**
|
||||||
|
|
||||||
|
**Email:**
|
||||||
|
alexa@blackroad.systems
|
||||||
|
|
||||||
|
**Office Hours:**
|
||||||
|
Monday - Friday, 9am - 5pm PST
|
||||||
|
|
||||||
|
**Response Time:**
|
||||||
|
We typically respond within 24 hours
|
||||||
|
|
||||||
|
**Design Partner Program:**
|
||||||
|
Looking for 5-10 design partners to pilot BlackRoad OS in production environments. Design partners receive:
|
||||||
|
- Early access to new features
|
||||||
|
- Dedicated engineering support
|
||||||
|
- Custom integrations
|
||||||
|
- Discounted pricing
|
||||||
|
- Co-marketing opportunities
|
||||||
|
|
||||||
|
### Map (Optional)
|
||||||
|
Placeholder for office location (if applicable)
|
||||||
|
|
||||||
|
### CTA
|
||||||
|
After form submission:
|
||||||
|
"Thank you! We'll be in touch within 24 hours."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Design System
|
||||||
|
|
||||||
|
### Colors
|
||||||
|
|
||||||
|
**Primary Palette:**
|
||||||
|
```css
|
||||||
|
--blackroad-teal: #00D9FF;
|
||||||
|
--blackroad-purple: #9D4EDD;
|
||||||
|
--blackroad-dark: #1a1a2e;
|
||||||
|
--blackroad-light: #f8f9fa;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Accent Colors:**
|
||||||
|
```css
|
||||||
|
--accent-green: #06FFA5;
|
||||||
|
--accent-orange: #FF6B35;
|
||||||
|
--accent-blue: #4EA8DE;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Gradients:**
|
||||||
|
```css
|
||||||
|
--gradient-hero: linear-gradient(135deg, #00D9FF 0%, #9D4EDD 100%);
|
||||||
|
--gradient-cta: linear-gradient(90deg, #06FFA5 0%, #00D9FF 100%);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Typography
|
||||||
|
|
||||||
|
**Fonts:**
|
||||||
|
- **Headings:** Inter (Bold, 700)
|
||||||
|
- **Body:** Inter (Regular, 400)
|
||||||
|
- **Code:** JetBrains Mono
|
||||||
|
|
||||||
|
**Sizes:**
|
||||||
|
```css
|
||||||
|
--h1: 4rem (64px)
|
||||||
|
--h2: 3rem (48px)
|
||||||
|
--h3: 2rem (32px)
|
||||||
|
--h4: 1.5rem (24px)
|
||||||
|
--body: 1rem (16px)
|
||||||
|
--small: 0.875rem (14px)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Components
|
||||||
|
|
||||||
|
**Buttons:**
|
||||||
|
- Primary: Gradient background, white text, rounded corners
|
||||||
|
- Secondary: Outline only, hover fills
|
||||||
|
- Ghost: Text only, underline on hover
|
||||||
|
|
||||||
|
**Cards:**
|
||||||
|
- White background
|
||||||
|
- Subtle shadow
|
||||||
|
- Rounded corners (8px)
|
||||||
|
- Hover: Lift effect (translateY)
|
||||||
|
|
||||||
|
**Forms:**
|
||||||
|
- Clean, minimal design
|
||||||
|
- Labels above inputs
|
||||||
|
- Focus states with accent color
|
||||||
|
- Validation messages
|
||||||
|
|
||||||
|
### Responsive Breakpoints
|
||||||
|
```css
|
||||||
|
--mobile: 320px - 768px
|
||||||
|
--tablet: 769px - 1024px
|
||||||
|
--desktop: 1025px+
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Technical Implementation
|
||||||
|
|
||||||
|
### Tech Stack
|
||||||
|
|
||||||
|
**Frontend:**
|
||||||
|
- HTML5
|
||||||
|
- CSS3 (with CSS Grid and Flexbox)
|
||||||
|
- Vanilla JavaScript (no framework needed for MVP)
|
||||||
|
- Optional: Alpine.js for interactivity
|
||||||
|
|
||||||
|
**Hosting:**
|
||||||
|
- **Option A:** Railway (same as backend)
|
||||||
|
- **Option B:** GitHub Pages (static)
|
||||||
|
- **Option C:** Cloudflare Pages
|
||||||
|
|
||||||
|
**Recommended:** Railway for unified deployment
|
||||||
|
|
||||||
|
### Build Process
|
||||||
|
|
||||||
|
**Simple approach:**
|
||||||
|
- No build step needed for Phase 1
|
||||||
|
- Direct HTML/CSS/JS
|
||||||
|
- Minify before deploy (optional)
|
||||||
|
|
||||||
|
**Future (Phase 2):**
|
||||||
|
- Add Vite for bundling
|
||||||
|
- Add Tailwind CSS for utility-first styling
|
||||||
|
- Add TypeScript for type safety
|
||||||
|
|
||||||
|
### Deployment
|
||||||
|
|
||||||
|
**Via Railway:**
|
||||||
|
1. Create static site service in Railway
|
||||||
|
2. Point to `/landing` directory
|
||||||
|
3. Configure custom domain: `blackroad.systems`
|
||||||
|
4. Deploy on push to main
|
||||||
|
|
||||||
|
**Via GitHub Pages:**
|
||||||
|
1. Create `gh-pages` branch
|
||||||
|
2. Copy landing page files
|
||||||
|
3. Configure custom domain CNAME
|
||||||
|
4. Deploy via GitHub Actions
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
**Optimization checklist:**
|
||||||
|
- [ ] Compress images (WebP format)
|
||||||
|
- [ ] Minify CSS/JS
|
||||||
|
- [ ] Enable Cloudflare caching
|
||||||
|
- [ ] Lazy load images
|
||||||
|
- [ ] Use CDN for assets
|
||||||
|
- [ ] Implement service worker (optional)
|
||||||
|
|
||||||
|
**Target metrics:**
|
||||||
|
- Lighthouse score: 90+
|
||||||
|
- First Contentful Paint: < 1.5s
|
||||||
|
- Time to Interactive: < 3s
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Content Guidelines
|
||||||
|
|
||||||
|
### Voice & Tone
|
||||||
|
|
||||||
|
**Voice:**
|
||||||
|
- Confident but not arrogant
|
||||||
|
- Technical but accessible
|
||||||
|
- Future-focused but practical
|
||||||
|
- Authoritative but friendly
|
||||||
|
|
||||||
|
**Tone Examples:**
|
||||||
|
|
||||||
|
**Good:**
|
||||||
|
- "Where AI meets the open road"
|
||||||
|
- "200+ agents. Zero black boxes."
|
||||||
|
- "Humans orchestrate. Agents execute."
|
||||||
|
|
||||||
|
**Avoid:**
|
||||||
|
- "Revolutionary AI platform" (too generic)
|
||||||
|
- "Cutting-edge machine learning" (too buzzwordy)
|
||||||
|
- "Industry-leading solution" (meaningless)
|
||||||
|
|
||||||
|
### Copywriting Principles
|
||||||
|
|
||||||
|
1. **Lead with benefits, not features**
|
||||||
|
- Not: "We have 200 agents"
|
||||||
|
- Instead: "Deploy 200 agents that work together autonomously"
|
||||||
|
|
||||||
|
2. **Use concrete numbers**
|
||||||
|
- Not: "Fast deployment"
|
||||||
|
- Instead: "Deploy in under 5 minutes"
|
||||||
|
|
||||||
|
3. **Show, don't tell**
|
||||||
|
- Not: "Easy to use"
|
||||||
|
- Instead: "One command to deploy: `railway up`"
|
||||||
|
|
||||||
|
4. **Address objections directly**
|
||||||
|
- Pain point → Solution → Proof
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Timeline
|
||||||
|
|
||||||
|
### Week 1: Design
|
||||||
|
- [ ] Create wireframes (Figma)
|
||||||
|
- [ ] Design hero section
|
||||||
|
- [ ] Design component library
|
||||||
|
- [ ] Get feedback from team
|
||||||
|
|
||||||
|
### Week 2: Development
|
||||||
|
- [ ] Build HTML structure
|
||||||
|
- [ ] Implement CSS styles
|
||||||
|
- [ ] Add JavaScript interactions
|
||||||
|
- [ ] Test responsiveness
|
||||||
|
|
||||||
|
### Week 3: Content
|
||||||
|
- [ ] Write all page copy
|
||||||
|
- [ ] Create placeholder images
|
||||||
|
- [ ] Set up contact form backend
|
||||||
|
- [ ] Add analytics (Google Analytics/Plausible)
|
||||||
|
|
||||||
|
### Week 4: Launch
|
||||||
|
- [ ] Deploy to Railway/GitHub Pages
|
||||||
|
- [ ] Configure custom domain
|
||||||
|
- [ ] Test across browsers
|
||||||
|
- [ ] Launch announcement
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Success Metrics
|
||||||
|
|
||||||
|
### Phase 1 Goals (First 3 Months)
|
||||||
|
|
||||||
|
**Traffic:**
|
||||||
|
- 1,000 unique visitors/month
|
||||||
|
- 2,000 page views/month
|
||||||
|
- 3-minute average session duration
|
||||||
|
|
||||||
|
**Conversions:**
|
||||||
|
- 50 demo requests
|
||||||
|
- 10 design partner applications
|
||||||
|
- 5 qualified design partners
|
||||||
|
|
||||||
|
**Engagement:**
|
||||||
|
- 20% docs link click-through
|
||||||
|
- 10% contact form submission rate
|
||||||
|
- 30% return visitor rate
|
||||||
|
|
||||||
|
### Tracking
|
||||||
|
|
||||||
|
**Tools:**
|
||||||
|
- Google Analytics (or Plausible for privacy-friendly alternative)
|
||||||
|
- Hotjar for heatmaps
|
||||||
|
- Form analytics via backend
|
||||||
|
|
||||||
|
**Key Events:**
|
||||||
|
- Demo request submitted
|
||||||
|
- Pricing page viewed
|
||||||
|
- Architecture page viewed
|
||||||
|
- Docs link clicked
|
||||||
|
- Contact form submitted
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
### Immediate Actions
|
||||||
|
|
||||||
|
1. **Create landing page repository** (or use existing)
|
||||||
|
2. **Set up basic HTML structure** (5 pages)
|
||||||
|
3. **Design hero section** (highest impact)
|
||||||
|
4. **Write homepage copy** (can iterate later)
|
||||||
|
5. **Deploy to staging** (test before production)
|
||||||
|
|
||||||
|
### Follow-Up
|
||||||
|
|
||||||
|
- Create email drip campaign for demo requests
|
||||||
|
- Set up automated demo scheduling (Calendly)
|
||||||
|
- Create sales playbook for design partner outreach
|
||||||
|
- Build case study template for early customers
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**This plan provides everything needed to build a professional, conversion-focused landing page for blackroad.systems. Execute in phases, measure results, and iterate based on real user feedback.**
|
||||||
|
|
||||||
|
**Where AI meets the open road.** 🛣️
|
||||||
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