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
|
- `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
|
- `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
|
## Preconditions
|
||||||
|
|
||||||
- You know the current Azure VM public IP.
|
- 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
|
- `200`, `302`, or auth-gated responses are acceptable depending on the app
|
||||||
- certificate issuance should complete automatically once DNS and ports are correct
|
- 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
|
## Troubleshooting
|
||||||
|
|
||||||
If records do not resolve as expected:
|
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
|
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
|
## Change Log
|
||||||
|
|
||||||
Use this section to record real DNS cutovers:
|
Use this section to record real DNS cutovers:
|
||||||
|
|
||||||
| Date | Operator | Change | Result |
|
| Date | Operator | Change | Result |
|
||||||
| ------------ | -------- | ------------------------------------------------------- | -------------- |
|
| ------------ | -------- | ------------------------------------------------------------------------------------------ | -------------------- |
|
||||||
| `2026-03-31` | Codex | Created GoDaddy-specific DNS runbook for `bytelyst.com` | Document added |
|
| `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}\"",
|
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md,yml,yaml}\"",
|
||||||
"audit": "pnpm -r audit --audit-level moderate",
|
"audit": "pnpm -r audit --audit-level moderate",
|
||||||
"clean": "pnpm -r exec rm -rf dist",
|
"clean": "pnpm -r exec rm -rf dist",
|
||||||
|
"dns:godaddy:bytelyst": "./scripts/godaddy-sync-bytelyst-dns.sh",
|
||||||
"prototype:self-test": "./scripts/prototype-self-test.sh",
|
"prototype:self-test": "./scripts/prototype-self-test.sh",
|
||||||
"prepare": "husky"
|
"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