Merge pull request #53 from beatz174-bit/codex/fix-dynu-api-authentication-issue

Auto-load secrets/dynu.env, harden Dynu credential handling, and update docs
This commit is contained in:
beatz174-bit
2026-04-21 13:38:52 +10:00
committed by GitHub
5 changed files with 46 additions and 5 deletions
+13 -1
View File
@@ -24,6 +24,18 @@ This repository includes a **read-only** Dynu DNS inventory workflow for `lan.dd
- `DYNU_BASE_URL` (optional, defaults to `https://api.dynu.com`)
- `DYNU_READ_ONLY` (**must** be `true`)
Recommended local secrets file (not committed): `secrets/dynu.env`
```bash
DYNU_API_KEY=replace-with-real-api-key
DYNU_READ_ONLY=true
DYNU_BASE_URL=https://api.dynu.com
```
Notes:
- Keep values unquoted unless required by your shell.
- `scripts/dynu/build_dns_inventory.sh` will auto-load `secrets/dynu.env` when present.
## Commands
Run directly:
@@ -36,7 +48,7 @@ DYNU_READ_ONLY=true python3 scripts/dynu/correlate_dynu_with_traefik.py
Or run the wrapper:
```bash
DYNU_READ_ONLY=true DYNU_API_KEY=... scripts/dynu/build_dns_inventory.sh
scripts/dynu/build_dns_inventory.sh
```
## Artifacts
+6 -2
View File
@@ -10,6 +10,7 @@ For machine-readable inventory metadata, use [`../secrets/inventory.json`](../se
- Canonical example template: [`../secrets/.env.secrets.example`](../secrets/.env.secrets.example)
- Runtime-loaded secret env file (local, non-committed): `../secrets/stack-secrets.env`
- Dynu DNS inventory env file (local, non-committed): `../secrets/dynu.env`
- Docker secret files (local, non-committed): `../secrets/*.txt`
Treat the example template as the canonical shape for expected environment variables.
@@ -20,9 +21,11 @@ Treat the example template as the canonical shape for expected environment varia
- Document expected variable names and usage expectations.
2. **Local runtime env file (`stack-secrets.env`)**
- Holds local runtime secret values loaded during compose rendering.
3. **Local Docker secret files (`*.txt`)**
3. **Local Dynu env file (`dynu.env`)**
- Holds `DYNU_*` values used by read-only Dynu DNS inventory scripts.
4. **Local Docker secret files (`*.txt`)**
- Hold password/token material consumed via `*_FILE` style configuration.
4. **Externally managed secret inputs**
5. **Externally managed secret inputs**
- Some values are managed outside shared templates and provided through file mounts or environment substitution.
## Machine-readable inventory
@@ -41,6 +44,7 @@ Before running compose operations, follow [`./deployment-prerequisites.md`](./de
Never commit:
- `secrets/stack-secrets.env`
- `secrets/dynu.env`
- real `secrets/*.txt` secret files
- real Terraform `.tfvars` files containing credentials
- Terraform state files with sensitive runtime metadata
+13
View File
@@ -4,10 +4,23 @@ set -euo pipefail
# This integration is intentionally read-only.
# No Dynu mutations are permitted in this repo at this stage.
# Optional convenience: auto-load local Dynu env file when variables are unset.
if [[ -f "secrets/dynu.env" ]]; then
set -a
# shellcheck source=/dev/null
source "secrets/dynu.env"
set +a
fi
if [[ "${DYNU_READ_ONLY:-}" != "true" ]]; then
echo "Refusing to run: DYNU_READ_ONLY must be exactly 'true'." >&2
exit 2
fi
if [[ -z "${DYNU_API_KEY:-}" ]]; then
echo "Missing DYNU_API_KEY. Set it in env or secrets/dynu.env." >&2
exit 2
fi
python3 scripts/dynu/fetch_dynu_dns.py
python3 scripts/dynu/correlate_dynu_with_traefik.py
+12 -2
View File
@@ -54,7 +54,13 @@ def get_json(base_url: str, api_key: str, path: str, query: Optional[Dict[str, A
body = resp.read().decode("utf-8")
except HTTPError as exc:
detail = exc.read().decode("utf-8", errors="replace")
raise RuntimeError(f"Dynu API GET failed at {path}: HTTP {exc.code} {detail}") from exc
hint = ""
if exc.code == 401:
hint = (
" Check DYNU_API_KEY from secrets/dynu.env, verify it is a valid Dynu API key, "
"and ensure DYNU_BASE_URL points to the Dynu API endpoint."
)
raise RuntimeError(f"Dynu API GET failed at {path}: HTTP {exc.code} {detail}.{hint}") from exc
except URLError as exc:
raise RuntimeError(f"Dynu API GET failed at {path}: {exc}") from exc
@@ -177,8 +183,12 @@ def main() -> int:
if not api_key:
print("Missing DYNU_API_KEY. Refusing to call Dynu API.", file=sys.stderr)
return 2
api_key = api_key.strip().strip("'").strip('"')
if not api_key:
print("DYNU_API_KEY is empty after trimming quotes/whitespace.", file=sys.stderr)
return 2
base_url = os.environ.get("DYNU_BASE_URL", DEFAULT_BASE_URL)
base_url = os.environ.get("DYNU_BASE_URL", DEFAULT_BASE_URL).strip().strip("'").strip('"')
domains = list_domains(base_url, api_key)
target = [d for d in domains if str(d.get("name", "")).strip(".").lower() == BASE_DOMAIN]
+2
View File
@@ -2,6 +2,7 @@
"scope_and_authority": {
"canonical_example_template": "secrets/.env.secrets.example",
"runtime_loaded_secret_env_file": "secrets/stack-secrets.env",
"dns_inventory_secret_env_file": "secrets/dynu.env",
"docker_secret_files_pattern": "secrets/*.txt"
},
"env_template_variables": [
@@ -140,6 +141,7 @@
],
"commit_safety_rules": [
"Never commit secrets/stack-secrets.env.",
"Never commit secrets/dynu.env.",
"Never commit real secrets/*.txt files.",
"Never commit real Terraform .tfvars containing credentials.",
"Never commit Terraform state files with sensitive runtime metadata."