docs(devops): capture godaddy dns cutover and vm handoff
This commit is contained in:
parent
6bb17e1d9c
commit
356c96e1d9
@ -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 |
|
||||
|
||||
@ -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"
|
||||
},
|
||||
|
||||
210
scripts/godaddy-sync-bytelyst-dns.sh
Executable file
210
scripts/godaddy-sync-bytelyst-dns.sh
Executable 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
|
||||
Loading…
Reference in New Issue
Block a user