Improve Dynu env handling and document secrets/dynu.env

This commit is contained in:
beatz174-bit
2026-04-21 13:38:33 +10:00
parent 8f112af65b
commit 749c0d500d
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_BASE_URL` (optional, defaults to `https://api.dynu.com`)
- `DYNU_READ_ONLY` (**must** be `true`) - `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 ## Commands
Run directly: Run directly:
@@ -36,7 +48,7 @@ DYNU_READ_ONLY=true python3 scripts/dynu/correlate_dynu_with_traefik.py
Or run the wrapper: Or run the wrapper:
```bash ```bash
DYNU_READ_ONLY=true DYNU_API_KEY=... scripts/dynu/build_dns_inventory.sh scripts/dynu/build_dns_inventory.sh
``` ```
## Artifacts ## 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) - Canonical example template: [`../secrets/.env.secrets.example`](../secrets/.env.secrets.example)
- Runtime-loaded secret env file (local, non-committed): `../secrets/stack-secrets.env` - 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` - Docker secret files (local, non-committed): `../secrets/*.txt`
Treat the example template as the canonical shape for expected environment variables. 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. - Document expected variable names and usage expectations.
2. **Local runtime env file (`stack-secrets.env`)** 2. **Local runtime env file (`stack-secrets.env`)**
- Holds local runtime secret values loaded during compose rendering. - 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. - 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. - Some values are managed outside shared templates and provided through file mounts or environment substitution.
## Machine-readable inventory ## Machine-readable inventory
@@ -41,6 +44,7 @@ Before running compose operations, follow [`./deployment-prerequisites.md`](./de
Never commit: Never commit:
- `secrets/stack-secrets.env` - `secrets/stack-secrets.env`
- `secrets/dynu.env`
- real `secrets/*.txt` secret files - real `secrets/*.txt` secret files
- real Terraform `.tfvars` files containing credentials - real Terraform `.tfvars` files containing credentials
- Terraform state files with sensitive runtime metadata - Terraform state files with sensitive runtime metadata
+13
View File
@@ -4,10 +4,23 @@ set -euo pipefail
# This integration is intentionally read-only. # This integration is intentionally read-only.
# No Dynu mutations are permitted in this repo at this stage. # 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 if [[ "${DYNU_READ_ONLY:-}" != "true" ]]; then
echo "Refusing to run: DYNU_READ_ONLY must be exactly 'true'." >&2 echo "Refusing to run: DYNU_READ_ONLY must be exactly 'true'." >&2
exit 2 exit 2
fi 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/fetch_dynu_dns.py
python3 scripts/dynu/correlate_dynu_with_traefik.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") body = resp.read().decode("utf-8")
except HTTPError as exc: except HTTPError as exc:
detail = exc.read().decode("utf-8", errors="replace") 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: except URLError as exc:
raise RuntimeError(f"Dynu API GET failed at {path}: {exc}") from exc raise RuntimeError(f"Dynu API GET failed at {path}: {exc}") from exc
@@ -177,8 +183,12 @@ def main() -> int:
if not api_key: if not api_key:
print("Missing DYNU_API_KEY. Refusing to call Dynu API.", file=sys.stderr) print("Missing DYNU_API_KEY. Refusing to call Dynu API.", file=sys.stderr)
return 2 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) domains = list_domains(base_url, api_key)
target = [d for d in domains if str(d.get("name", "")).strip(".").lower() == BASE_DOMAIN] 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": { "scope_and_authority": {
"canonical_example_template": "secrets/.env.secrets.example", "canonical_example_template": "secrets/.env.secrets.example",
"runtime_loaded_secret_env_file": "secrets/stack-secrets.env", "runtime_loaded_secret_env_file": "secrets/stack-secrets.env",
"dns_inventory_secret_env_file": "secrets/dynu.env",
"docker_secret_files_pattern": "secrets/*.txt" "docker_secret_files_pattern": "secrets/*.txt"
}, },
"env_template_variables": [ "env_template_variables": [
@@ -140,6 +141,7 @@
], ],
"commit_safety_rules": [ "commit_safety_rules": [
"Never commit secrets/stack-secrets.env.", "Never commit secrets/stack-secrets.env.",
"Never commit secrets/dynu.env.",
"Never commit real secrets/*.txt files.", "Never commit real secrets/*.txt files.",
"Never commit real Terraform .tfvars containing credentials.", "Never commit real Terraform .tfvars containing credentials.",
"Never commit Terraform state files with sensitive runtime metadata." "Never commit Terraform state files with sensitive runtime metadata."