# DNS Management

Manage CULTSCALE DNS zones as code using [OctoDNS](https://github.com/octodns/octodns). Zone files are stored as YAML and automatically synced to Cloudflare via GitHub Actions.

## Requirements

- Python 3.x
- `octodns` and `octodns-cloudflare` (install with `make install`)
- `yq` - YAML processor (for Makefile commands)
  - Install: `pkg install yq` (Termux) or `brew install yq` (macOS) or see https://github.com/mikefarah/yq

## Quick Start

### 1. Setup GitHub Secret

Prefer adding `CLOUDFLARE_TOKEN` to repository secrets (OctoDNS reads `CLOUDFLARE_TOKEN`). If you already have one, `CLOUDFLARE_DNS_TOKEN` or `CLOUDFLARE_API_TOKEN` will also work in CI.
- Go to: Settings → Secrets and variables → Actions → New repository secret
- Name: `CLOUDFLARE_TOKEN`
- Value: Token from https://dash.cloudflare.com/profile/api-tokens (use "Edit zone DNS" template)
- Required permissions: Zone:Read, DNS:Edit

### 2. Making Changes

Edit zone files in `zones/`:

```yaml
# zones/cultscale.com.yaml
subdomain:
  type: A
  value: 192.0.2.1
  ttl: 300
```

Commit and push:
```bash
git add dns/zones/cultscale.com.yaml
git commit -m "Add subdomain"
git push
```

GitHub Actions will validate on PR, sync on merge to `main`.

## Zone File Format

```yaml
# Root domain (@ in DNS)
'':
  type: A
  value: 192.0.2.1
  ttl: 300

# Root domain CNAME - MUST use ALIAS type (not CNAME)
'':
  octodns:
    cloudflare:
      proxied: true
  type: ALIAS  # Required: OctoDNS rejects root CNAME per RFC
  value: cultscale-web.pages.dev.

# Subdomain
www:
  type: CNAME
  value: cultscale-web.pages.dev.
  ttl: 300

# Multiple values
'':
  type: MX
  values:
    - exchange: mx1.example.com.
      preference: 10
    - exchange: mx2.example.com.
      preference: 20
  ttl: 3600

# TXT records
'':
  type: TXT
  values:
    - "v=spf1 include:_spf.google.com ~all"
    - "google-site-verification=abc123"
  ttl: 300
```

**Key points:**
- Root domain: `''` (empty string)
- Domain names in values need trailing `.`: `example.com.`
- Use `value:` for single, `values:` for multiple
- **Root CNAMEs: Use `type: ALIAS` not `type: CNAME` (OctoDNS RFC requirement)**
- Subdomain keys don't need trailing dots: `www:` not `www.:`

## Common Records

**A record (IP address):**
```yaml
subdomain:
  type: A
  value: 192.0.2.1
  ttl: 300
```

**CNAME (alias):**
```yaml
www:
  type: CNAME
  value: target.example.com.
  ttl: 300
```

**MX (email):**
```yaml
'':
  type: MX
  values:
    - exchange: mx1.example.com.
      preference: 10
  ttl: 3600
```

**TXT (verification, SPF, DKIM):**
```yaml
'':
  type: TXT
  value: "v=spf1 include:_spf.google.com ~all"
  ttl: 300
```

## Makefile Commands

Quick commands for common tasks:

```bash
make help        # Show all available commands
make status      # Check configuration and zones
make validate    # Validate changes (dry-run)
make sync        # Apply changes to Cloudflare
make dump        # Export Cloudflare state to YAML
make report      # Show detailed zone information
make install     # Install OctoDNS dependencies
```

**Requirements:** `yq` must be installed (`pkg install yq` on Termux)

## Local Validation

Install OctoDNS locally to test changes:

```bash
pip install octodns octodns-cloudflare
export CLOUDFLARE_TOKEN="your_token"
cd dns
octodns-sync --config-file=config.yaml
```

## Manual Sync

Emergency DNS updates without PR/merge:

```bash
export CLOUDFLARE_TOKEN="your_token"
cd dns
octodns-sync --config-file=config.yaml --doit
```

## Export Zones

To export current DNS records from Cloudflare:

```bash
pip install octodns octodns-cloudflare
export CLOUDFLARE_TOKEN="your_token"
cd dns

# Create temporary export config
tmpdir="${TMPDIR:-/tmp}"; cat > "$tmpdir/export-config.yaml" << 'EOF'
providers:
  config:
    class: octodns.provider.yaml.YamlProvider
    directory: ./zones
  cloudflare:
    class: octodns_cloudflare.CloudflareProvider
    token: env/CLOUDFLARE_TOKEN

zones:
  cultscale.com.:
    sources: [cloudflare]
    targets: [config]
EOF

octodns-sync --config-file="$tmpdir/export-config.yaml" --doit
```

## Configured Zones

11 zones total (see `config.yaml`):
- cultborn.com
- cultreel.com
- cultscale.com, cultscale.net, cultscale.org
- cultshot.com
- cultsonic.com
- cultsync.com
- michapp.org
- yusi.app, yusiapp.com

## Bulk Redirects (GitOps)

- Source file: `dns/bulk-redirects.json`
- Sync workflow: `.github/workflows/sync-bulk-redirects.yml`
- Secret: `CLOUDFLARE_TOKEN` (preferred) or `CLOUDFLARE_DNS_TOKEN` or `CLOUDFLARE_API_TOKEN` (scopes: `account_filter_lists:edit`, `account:read`); workflow skips if no secret is set
- Behavior: workflow replaces the Cloudflare bulk redirect list with the JSON contents on `main` or manual dispatch.

## Conflict Resolution

**OctoDNS is source-of-truth based:** YAML files are authoritative, Cloudflare is the target.

**What happens if DNS is changed in Cloudflare UI?**

When you sync, OctoDNS will:
1. Read your YAML files (source)
2. Read Cloudflare current state (target)
3. Calculate diff (what needs to change)
4. **Overwrite Cloudflare with YAML content**

**Your YAML file wins. Cloudflare UI changes are lost.**

**Example:**
```
YAML:       www → 192.0.2.1
Cloudflare: www → 203.0.113.50 (someone changed via UI)

Result: OctoDNS updates Cloudflare to 192.0.2.1 (YAML wins)
```

**To preserve Cloudflare UI changes:**

```bash
# Export Cloudflare state to YAML
tmpdir="${TMPDIR:-/tmp}"; octodns-sync --config-file="$tmpdir/export.yaml" --doit

# Review changes
git diff zones/

# Commit if good
git add zones/
git commit -m "Import DNS changes from Cloudflare UI"
```

**Best practices:**
- Treat YAML files as the only source of truth
- Avoid editing DNS in Cloudflare UI
- If you must use UI, export immediately after
- Always review the plan output before applying (dry-run first)
- Use PRs for changes (team visibility and review)

## Troubleshooting

**YAML syntax errors:** Check indentation (2 spaces), quotes, trailing dots  
**No changes detected:** File matches Cloudflare state  
**Permission denied:** Verify token hasn't expired, has DNS:Edit permission  
**Validation fails:** Run `octodns-sync --config-file=config.yaml --debug` for details

## Resources

- [OctoDNS Docs](https://github.com/octodns/octodns)
- [Cloudflare API](https://developers.cloudflare.com/api/)
