Merge commit 'a23ec040f1ec1152cbb96e6b51e1e597b0c35a36'

This commit is contained in:
Alexa Amundson
2025-11-17 00:45:18 -06:00
4 changed files with 118 additions and 0 deletions

46
.github/workflows/domain-health.yml vendored Normal file
View File

@@ -0,0 +1,46 @@
name: Domain Health Check
on:
schedule:
- cron: "0 */6 * * *" # every 6 hours
workflow_dispatch: {}
jobs:
health:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pyyaml requests
- name: Run health checks
env:
# Add any API keys if needed for your health checks (not required for simple HTTP GET)
run: |
python - <<'PY'
import yaml, requests, os
with open("ops/domains.yaml") as f:
cfg = yaml.safe_load(f)
for d in cfg.get("domains", []):
# Only health-check DNS records pointing to HTTP(S) targets
health = d.get("healthcheck", False)
if d.get("mode") == "dns" and health:
url = "https://" + d["name"]
try:
r = requests.get(url, timeout=10)
status = "healthy" if r.status_code < 400 else f"unhealthy (status {r.status_code})"
except Exception as e:
status = f"unhealthy (error {e})"
print(f"{d['name']}: {status}")
PY

63
docs/domains-overview.md Normal file
View File

@@ -0,0 +1,63 @@
# Domain Management Overview
This repository includes a **universal domain orchestrator** that allows you to declare all of your domains in a single YAML file and sync them to your DNS providers automatically. The goal is to avoid manual updates in registrar dashboards by defining a clear desired state and letting automation converge on it.
## Configuration File (`ops/domains.yaml`)
The `ops/domains.yaml` file lists each domain or subdomain with the following fields:
- **name**: The fully qualified domain or subdomain (e.g. `blackroad.systems`, `os.blackroad.systems`).
- **type**: `"root"` for an apex domain or `"subdomain"` for a subdomain.
- **provider**: Which registrar/DNS provider the domain lives in (`godaddy` or `cloudflare`).
- **mode**:
- `forward` perform a 301 (permanent) redirect to another URL.
- `dns` create or update a DNS record (CNAME or A).
- **forward_to** (only for `forward`): The destination URL to forward to.
- **forwarding_type** (optional): HTTP status code for forwarding (301 by default).
- **record** (only for `dns`): A mapping with `type` (`CNAME` or `A`) and `value` (target hostname or IP).
- **healthcheck** (optional): Set to `true` to enable periodic health checks in the optional workflow.
Add as many entries as needed; the script processes them sequentially.
## Domain Sync Script (`ops/scripts/apply_domains.py`)
This Python script:
1. Parses `ops/domains.yaml`.
2. For GoDaddy domains:
- Uses the GoDaddy API to set forwarding rules or DNS records.
- A permanent 301 redirect tells search engines to treat the destination as the canonical URL.
3. For Cloudflare domains:
- Finds the zone ID for the root domain.
- Creates or updates DNS records using the Cloudflare API, which supports PUT/PATCH requests to overwrite DNS entries.
4. Prints a summary of actions taken; if nothing changed, it reports that the record is up to date.
All credentials (GoDaddy key/secret and Cloudflare token) are read from environment variables. **Never hardcode secrets.** Set them as GitHub repository secrets (Settings → Secrets and variables → Actions).
## GitHub Actions Workflows
### `sync-domains.yml`
- Runs on pushes to `main` where `ops/domains.yaml` changes, or via manual trigger.
- Checks out the repo, installs Python dependencies, and runs the sync script.
- Uses secrets to authenticate to GoDaddy and Cloudflare.
- Annotates logs with changes so you can see which domains were updated.
### `domain-health.yml` (optional)
- Runs every six hours (or manually).
- Reads `ops/domains.yaml` and performs an HTTP GET against any domain marked `healthcheck: true`.
- Logs whether the domain is reachable; you can extend it to open GitHub Issues on repeated failures.
## How To Add or Modify Domains
1. Open `ops/domains.yaml` and add a new entry for each domain or subdomain you own.
2. Specify its `provider`, `mode`, and either `forward_to` or a DNS `record`.
3. Commit and push your changes to `main`.
4. Ensure the following secrets are configured in GitHub:
- `GODADDY_API_KEY`, `GODADDY_API_SECRET`
- `CLOUDFLARE_TOKEN`
5. Run the **Sync Domains** workflow manually (Actions → Sync Domains → Run workflow) or wait for the push trigger.
6. Verify your DNS or forwarding settings with `dig` or by visiting the domains.
By following this process, you maintain a single source of truth for all of your domains and eliminate the need to log into registrar dashboards for each change.

View File

@@ -27,6 +27,7 @@ domains:
record: record:
type: "CNAME" type: "CNAME"
value: "blackroad-operating-system-production.up.railway.app" value: "blackroad-operating-system-production.up.railway.app"
value: "YOUR-PROD-RAILWAY-APP.up.railway.app" # replace with your Railway host
- name: "blackroad.ai" - name: "blackroad.ai"
type: "root" type: "root"
@@ -51,3 +52,7 @@ domains:
record: record:
type: "CNAME" type: "CNAME"
value: "os.blackroad.systems" value: "os.blackroad.systems"
# Add additional domains or subdomains here following the same pattern.
# By default you can set mode: "dns" with a CNAME pointing to os.blackroad.systems,
# or override as needed.

4
ops/scripts/apply_domains.py Normal file → Executable file
View File

@@ -3,6 +3,7 @@
apply_domains.py apply_domains.py
Reads ops/domains.yaml and ensures that each domains DNS/forwarding Reads ops/domains.yaml and ensures that each domains DNS/forwarding
Reads ops/domains.yaml and ensures that each domain's DNS/forwarding
settings are correctly applied via GoDaddy and Cloudflare APIs. settings are correctly applied via GoDaddy and Cloudflare APIs.
This script is designed to be idempotent: re-running it will not create This script is designed to be idempotent: re-running it will not create
duplicate records. duplicate records.
@@ -188,6 +189,7 @@ def update_cloudflare_dns_record(domain_entry):
if not zone_id: if not zone_id:
return return
# Determine the record name field expected by CF (full domain)
record_name = full_name record_name = full_name
# List existing records to find match # List existing records to find match
@@ -202,6 +204,7 @@ def update_cloudflare_dns_record(domain_entry):
results = [] results = []
if results: if results:
# update existing record if value differs
record_id = results[0]["id"] record_id = results[0]["id"]
current_value = results[0]["content"] current_value = results[0]["content"]
if current_value == record_value: if current_value == record_value:
@@ -216,6 +219,7 @@ def update_cloudflare_dns_record(domain_entry):
except requests.HTTPError as e: except requests.HTTPError as e:
print(f"Error updating Cloudflare record for {full_name}: {e}") print(f"Error updating Cloudflare record for {full_name}: {e}")
else: else:
# create new record
payload = {"type": record_type, "name": record_name, "content": record_value, "ttl": 300, "proxied": False} payload = {"type": record_type, "name": record_name, "content": record_value, "ttl": 300, "proxied": False}
print(f"Creating Cloudflare record for {full_name}: {record_type} -> {record_value}") print(f"Creating Cloudflare record for {full_name}: {record_type} -> {record_value}")
try: try: