docs(devops): capture godaddy dns cutover and vm handoff

This commit is contained in:
Saravana Achu Mac 2026-03-31 02:25:58 -07:00
parent 6bb17e1d9c
commit 356c96e1d9
3 changed files with 413 additions and 3 deletions

View File

@ -28,6 +28,55 @@ Notes:
- `llmlab-dashboard` is internal VM tooling and does not need a public DNS record
- `localmemgpt-web` is intended to be hosted on Vercel, so it is not part of this VM DNS runbook
## Automated Option
If GoDaddy API credentials are already configured, use the repo script instead of editing records by hand:
```bash
./scripts/godaddy-sync-bytelyst-dns.sh --ip <Azure VM public IP> --validate
```
Or auto-detect the current machine public IP:
```bash
./scripts/godaddy-sync-bytelyst-dns.sh --auto-ip --dry-run
./scripts/godaddy-sync-bytelyst-dns.sh --auto-ip --validate
```
Credential lookup order:
- `GODADDY_API_KEY` and `GODADDY_API_SECRET`
- `~/.config/godaddypy/credentials.yaml`
The script manages these `A` records by default:
- `api`
- `gitea`
- `admin`
- `tracker`
Root shortcut:
```bash
pnpm dns:godaddy:bytelyst -- --ip <Azure VM public IP> --validate
```
## Current Status
Status as of `2026-03-31 09:23:20 UTC`:
- GoDaddy `A` records were updated for `api`, `gitea`, `admin`, and `tracker`
- all four names now resolve to `187.124.159.82`
- authoritative GoDaddy nameservers returned the expected IP for all four names
- public `dig` checks also returned `187.124.159.82`
- public HTTP on port `80` responded, but returned `404 Not Found`
- public HTTPS on port `443` timed out for all four hostnames
Interpretation:
- DNS cutover is complete
- the remaining work is on the VM-side gateway and/or Azure network path, not in GoDaddy DNS
## Preconditions
- You know the current Azure VM public IP.
@ -85,6 +134,147 @@ Expected result:
- `200`, `302`, or auth-gated responses are acceptable depending on the app
- certificate issuance should complete automatically once DNS and ports are correct
## Next Action For Codex On The VM
Delegate the remaining work to the Codex session running inside the Azure VM. The immediate goal is to make `443` work and ensure the hostnames are actually configured in Caddy.
Recommended handoff summary:
- DNS is already correct for `api.bytelyst.com`, `gitea.bytelyst.com`, `admin.bytelyst.com`, and `tracker.bytelyst.com`
- all four names point to `187.124.159.82`
- do not spend time redoing GoDaddy changes unless records drift
- focus on `/opt/bytelyst/Caddyfile`, the `caddy` container, and Azure NSG rules for `443`
Run these on the VM:
```bash
cd /opt/bytelyst/learning_ai_common_plat
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
docker logs caddy --tail 200
docker exec caddy caddy validate --config /etc/caddy/Caddyfile
sudo ss -ltnp | grep -E ':80|:443'
curl -sI http://localhost:3001 | head -5
curl -sI http://localhost:3003 | head -5
curl -sI http://localhost:3300 | head -5
curl -sI http://localhost:4003/health | head -5
```
Check the live Caddy config:
```bash
sed -n '1,220p' /opt/bytelyst/Caddyfile
```
The live Caddy config should cover at least these hostnames:
- `api.bytelyst.com`
- `gitea.bytelyst.com`
- `admin.bytelyst.com`
- `tracker.bytelyst.com`
Expected proxy targets:
- `api.bytelyst.com`:
- `/platform/*` -> `platform-service:4003`
- `/extraction/*` -> `extraction-service:4005`
- `/mcp/*` -> `mcp-server:4007`
- `gitea.bytelyst.com` -> host or container endpoint for Gitea on port `3300`
- `admin.bytelyst.com` -> `admin-web:3001`
- `tracker.bytelyst.com` -> `tracker-web:3003`
If the file is missing host blocks, update it and reload Caddy:
```bash
docker exec caddy caddy reload --config /etc/caddy/Caddyfile
docker logs caddy --tail 100
```
If `443` still times out after Caddy config is corrected:
- verify the Azure NSG allows inbound `443/tcp`
- verify no other process is conflicting with port `443`
- verify the `caddy` container actually publishes `0.0.0.0:443->443/tcp`
Re-test from the VM:
```bash
curl -vk https://api.bytelyst.com/platform/health
curl -vk https://gitea.bytelyst.com
curl -vk https://admin.bytelyst.com
curl -vk https://tracker.bytelyst.com
```
Ready-to-paste prompt for the Codex session running inside the VM:
```text
The GoDaddy DNS cutover is already complete. Do not redo DNS unless records drift.
Known-good DNS state as of 2026-03-31:
- api.bytelyst.com -> 187.124.159.82
- gitea.bytelyst.com -> 187.124.159.82
- admin.bytelyst.com -> 187.124.159.82
- tracker.bytelyst.com -> 187.124.159.82
Known current failure:
- HTTP on port 80 responds, but returns 404
- HTTPS on port 443 times out for all four hostnames
Your task:
1. Inspect the live VM deployment under /opt/bytelyst
2. Read /opt/bytelyst/Caddyfile
3. Verify the caddy container is running and publishing 80/443
4. Verify Azure-side access assumptions from inside the VM as far as possible
5. Fix the live Caddy config so these hosts are served:
- api.bytelyst.com
- gitea.bytelyst.com
- admin.bytelyst.com
- tracker.bytelyst.com
6. Ensure proxy targets are correct:
- api.bytelyst.com:
- /platform/* -> platform-service:4003
- /extraction/* -> extraction-service:4005
- /mcp/* -> mcp-server:4007
- gitea.bytelyst.com -> Gitea on port 3300
- admin.bytelyst.com -> admin-web:3001
- tracker.bytelyst.com -> tracker-web:3003
7. Reload Caddy
8. Verify:
- curl -vk https://api.bytelyst.com/platform/health
- curl -vk https://gitea.bytelyst.com
- curl -vk https://admin.bytelyst.com
- curl -vk https://tracker.bytelyst.com
Run these first:
cd /opt/bytelyst/learning_ai_common_plat
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
docker logs caddy --tail 200
docker exec caddy caddy validate --config /etc/caddy/Caddyfile
sudo ss -ltnp | grep -E ':80|:443'
sed -n '1,220p' /opt/bytelyst/Caddyfile
curl -sI http://localhost:3001 | head -5
curl -sI http://localhost:3003 | head -5
curl -sI http://localhost:3300 | head -5
curl -sI http://localhost:4003/health | head -5
If /opt/bytelyst/Caddyfile is missing host blocks, fix it there and reload:
docker exec caddy caddy reload --config /etc/caddy/Caddyfile
docker logs caddy --tail 100
If 443 still times out after the config fix:
- verify Azure NSG allows inbound 443/tcp
- verify no other process is bound to 443
- verify the caddy container publishes 0.0.0.0:443->443/tcp
When done, report:
- what was wrong
- what file(s) you changed
- exact verification results for all four public hostnames
```
## Troubleshooting
If records do not resolve as expected:
@ -102,10 +292,19 @@ docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' | grep -E 'caddy|
docker logs caddy --tail 100
```
Likely root causes for the current state:
- the live `/opt/bytelyst/Caddyfile` only includes `api.bytelyst.com`
- `gitea`, `admin`, and `tracker` host blocks were never added on the VM
- Azure NSG is allowing `80` but not `443`
- Caddy is not healthy or is failing certificate issuance / bind on `443`
## Change Log
Use this section to record real DNS cutovers:
| Date | Operator | Change | Result |
| ------------ | -------- | ------------------------------------------------------- | -------------- |
| `2026-03-31` | Codex | Created GoDaddy-specific DNS runbook for `bytelyst.com` | Document added |
| Date | Operator | Change | Result |
| ------------ | -------- | ------------------------------------------------------------------------------------------ | -------------------- |
| `2026-03-31` | Codex | Created GoDaddy-specific DNS runbook for `bytelyst.com` | Document added |
| `2026-03-31` | Codex | Updated GoDaddy `A` records for `api`, `gitea`, `admin`, and `tracker` to `187.124.159.82` | DNS cutover complete |
| `2026-03-31` | Codex | Verified DNS propagation and recorded VM-side HTTPS follow-up steps | VM action pending |

View File

@ -18,6 +18,7 @@
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md,yml,yaml}\"",
"audit": "pnpm -r audit --audit-level moderate",
"clean": "pnpm -r exec rm -rf dist",
"dns:godaddy:bytelyst": "./scripts/godaddy-sync-bytelyst-dns.sh",
"prototype:self-test": "./scripts/prototype-self-test.sh",
"prepare": "husky"
},

View File

@ -0,0 +1,210 @@
#!/usr/bin/env bash
set -euo pipefail
DOMAIN="${GODADDY_DOMAIN:-bytelyst.com}"
TTL="${GODADDY_DNS_TTL:-600}"
TARGET_IP="${GODADDY_DNS_TARGET_IP:-}"
AUTO_IP=false
DRY_RUN=false
VALIDATE=false
HOSTS=("api" "gitea" "admin" "tracker")
CONFIG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/godaddypy/credentials.yaml"
usage() {
cat <<'USAGE'
Upsert ByteLyst GoDaddy A records for the Azure VM cutover.
Usage:
scripts/godaddy-sync-bytelyst-dns.sh [options]
Options:
--ip <address> Target IPv4 address for all managed A records
--auto-ip Detect the current public IP with api.ipify.org
--domain <domain> GoDaddy zone to update (default: bytelyst.com)
--ttl <seconds> TTL for the managed A records (default: 600)
--hosts <csv> Hostnames to manage (default: api,gitea,admin,tracker)
--validate Run dig validation after changes
--dry-run Print the API operations without applying them
-h, --help Show help
Credentials:
- Preferred: GODADDY_API_KEY and GODADDY_API_SECRET
- Fallback: ~/.config/godaddypy/credentials.yaml
Examples:
scripts/godaddy-sync-bytelyst-dns.sh --ip 20.81.12.34 --validate
scripts/godaddy-sync-bytelyst-dns.sh --auto-ip --dry-run
USAGE
}
while [[ $# -gt 0 ]]; do
case "$1" in
--ip)
TARGET_IP="${2:-}"
shift 2
;;
--auto-ip)
AUTO_IP=true
shift
;;
--domain)
DOMAIN="${2:-}"
shift 2
;;
--ttl)
TTL="${2:-}"
shift 2
;;
--hosts)
IFS=',' read -r -a HOSTS <<<"${2:-}"
shift 2
;;
--validate)
VALIDATE=true
shift
;;
--dry-run)
DRY_RUN=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown arg: $1" >&2
usage
exit 2
;;
esac
done
trim() {
local value="$1"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
printf '%s' "$value"
}
load_credential_from_file() {
local key_name="$1"
if [[ ! -f "$CONFIG_FILE" ]]; then
return 1
fi
sed -nE "s/^[[:space:]]*${key_name}:[[:space:]]*['\"]?([^'\"]+)['\"]?[[:space:]]*$/\\1/p" "$CONFIG_FILE" | head -n 1
}
GODADDY_API_KEY="${GODADDY_API_KEY:-}"
GODADDY_API_SECRET="${GODADDY_API_SECRET:-}"
if [[ -z "$GODADDY_API_KEY" ]]; then
GODADDY_API_KEY="$(load_credential_from_file key || true)"
fi
if [[ -z "$GODADDY_API_SECRET" ]]; then
GODADDY_API_SECRET="$(load_credential_from_file secret || true)"
fi
if [[ -z "$GODADDY_API_KEY" || -z "$GODADDY_API_SECRET" ]]; then
echo "Missing GoDaddy credentials. Set GODADDY_API_KEY and GODADDY_API_SECRET, or configure $CONFIG_FILE." >&2
exit 2
fi
if [[ "$AUTO_IP" == true ]]; then
TARGET_IP="$(curl -fsS https://api.ipify.org)"
fi
if [[ -z "$TARGET_IP" ]]; then
echo "Missing target IP. Use --ip <address> or --auto-ip." >&2
exit 2
fi
if [[ ! "$TARGET_IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
echo "Target IP does not look like an IPv4 address: $TARGET_IP" >&2
exit 2
fi
if [[ ! "$TTL" =~ ^[0-9]+$ ]]; then
echo "TTL must be numeric: $TTL" >&2
exit 2
fi
if ! command -v curl >/dev/null 2>&1; then
echo "curl is required." >&2
exit 1
fi
api_request() {
local method="$1"
local path="$2"
local body="${3:-}"
local -a curl_args=(
-fsS
-X "$method"
-H "Authorization: sso-key ${GODADDY_API_KEY}:${GODADDY_API_SECRET}"
-H "Accept: application/json"
"https://api.godaddy.com${path}"
)
if [[ -n "$body" ]]; then
curl_args+=(-H "Content-Type: application/json" --data "$body")
fi
curl "${curl_args[@]}"
}
printf 'Domain: %s\n' "$DOMAIN"
printf 'Target IP: %s\n' "$TARGET_IP"
printf 'TTL: %s\n' "$TTL"
printf 'Hosts: %s\n' "$(IFS=,; printf '%s' "${HOSTS[*]}")"
for raw_host in "${HOSTS[@]}"; do
host="$(trim "$raw_host")"
if [[ -z "$host" ]]; then
continue
fi
path="/v1/domains/${DOMAIN}/records/A/${host}"
payload=$(printf '[{"data":"%s","ttl":%s}]' "$TARGET_IP" "$TTL")
echo
printf 'Record: %s.%s\n' "$host" "$DOMAIN"
if [[ "$DRY_RUN" == true ]]; then
printf 'Dry run: PUT %s %s\n' "$path" "$payload"
continue
fi
current_records="$(api_request GET "$path" || true)"
if [[ -n "$current_records" ]]; then
printf 'Existing A records: %s\n' "$current_records"
else
echo "Existing A records: none"
fi
api_request PUT "$path" "$payload" >/dev/null
echo "Updated A record."
done
if [[ "$VALIDATE" == true ]]; then
echo
echo "Validation:"
if ! command -v dig >/dev/null 2>&1; then
echo "dig not found; skipping DNS validation." >&2
exit 0
fi
for raw_host in "${HOSTS[@]}"; do
host="$(trim "$raw_host")"
if [[ -z "$host" ]]; then
continue
fi
resolved_ip="$(dig +short "${host}.${DOMAIN}" | tail -n 1)"
printf '%s.%s -> %s\n' "$host" "$DOMAIN" "${resolved_ip:-<no answer>}"
done
fi