Add Pages Steam secret put helpers for climperor-relations.
Documents staging via keyzoo so production /api/auth/steam gets STEAM_API_KEY and SESSION_SECRET. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -29,10 +29,15 @@ npx wrangler@3 secret put OPENDOTA_API_KEY
|
|||||||
|
|
||||||
Pages project `climperor-relations` needs bindings `DB`, `MATCHES`, `SYNC_QUEUE`
|
Pages project `climperor-relations` needs bindings `DB`, `MATCHES`, `SYNC_QUEUE`
|
||||||
(see `web/frontend/wrangler.toml`). `provision.py` binds production **and** preview
|
(see `web/frontend/wrangler.toml`). `provision.py` binds production **and** preview
|
||||||
with matching `fail_open`. Secrets already used by auth:
|
with matching `fail_open`. Auth secrets on Pages (prod+preview):
|
||||||
`STEAM_API_KEY`, `SESSION_SECRET`, optional `PLAYER_PAGES_PUBLISH_SECRET`.
|
|
||||||
Worker also needs `STEAM_API_KEY` (`python web/cloudflare/put_worker_secrets.py`
|
```powershell
|
||||||
after staging via `_stage_steam_key.py`).
|
python web/cloudflare/_stage_steam_auth.py # via keyzoo refining/steam
|
||||||
|
python web/cloudflare/put_pages_secrets.py # via keyzoo refining/cloudflare
|
||||||
|
```
|
||||||
|
|
||||||
|
Optional `PLAYER_PAGES_PUBLISH_SECRET`. Worker also needs `STEAM_API_KEY`
|
||||||
|
(`python web/cloudflare/put_worker_secrets.py` after `_stage_steam_key.py`).
|
||||||
|
|
||||||
**R2**: if create returns “enable R2”, open
|
**R2**: if create returns “enable R2”, open
|
||||||
https://dash.cloudflare.com/?to=/:account/r2 once, then re-run `provision.py`
|
https://dash.cloudflare.com/?to=/:account/r2 once, then re-run `provision.py`
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
"""Dump Pages env_var names/types and latest deployment id (no secret values)."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
ACCT = "510534f7f6284344aadaf2f5a0794d48"
|
||||||
|
PROJ = "climperor-relations"
|
||||||
|
|
||||||
|
|
||||||
|
def get(path: str) -> dict:
|
||||||
|
email = os.environ.get("CLOUDFLARE_EMAIL") or os.environ.get(
|
||||||
|
"KEYZOO_ASSET_META_USERNAME"
|
||||||
|
)
|
||||||
|
key = os.environ.get("CLOUDFLARE_API_KEY") or os.environ.get(
|
||||||
|
"KEYZOO_ASSET_SECRET_GLOBAL_API_KEY"
|
||||||
|
)
|
||||||
|
req = urllib.request.Request(
|
||||||
|
f"https://api.cloudflare.com/client/v4{path}",
|
||||||
|
headers={"X-Auth-Email": email, "X-Auth-Key": key},
|
||||||
|
)
|
||||||
|
with urllib.request.urlopen(req, timeout=60) as r:
|
||||||
|
return json.load(r)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
p = get(f"/accounts/{ACCT}/pages/projects/{PROJ}")["result"]
|
||||||
|
dc = p.get("deployment_configs") or {}
|
||||||
|
for env in ("production", "preview"):
|
||||||
|
ev = (dc.get(env) or {}).get("env_vars") or {}
|
||||||
|
print(env, "keys", sorted(ev.keys()))
|
||||||
|
for k, v in sorted(ev.items()):
|
||||||
|
print(
|
||||||
|
" ",
|
||||||
|
k,
|
||||||
|
"type=",
|
||||||
|
(v or {}).get("type"),
|
||||||
|
"has_value=",
|
||||||
|
"value" in (v or {}),
|
||||||
|
)
|
||||||
|
deps = get(f"/accounts/{ACCT}/pages/projects/{PROJ}/deployments?per_page=2")[
|
||||||
|
"result"
|
||||||
|
]
|
||||||
|
for d0 in deps or []:
|
||||||
|
print(
|
||||||
|
"deployment",
|
||||||
|
d0.get("id"),
|
||||||
|
d0.get("environment"),
|
||||||
|
d0.get("created_on"),
|
||||||
|
d0.get("url"),
|
||||||
|
)
|
||||||
|
if d0.get("id"):
|
||||||
|
det = get(
|
||||||
|
f"/accounts/{ACCT}/pages/projects/{PROJ}/deployments/{d0['id']}"
|
||||||
|
)["result"]
|
||||||
|
# Some APIs nest env under build_config / env_vars
|
||||||
|
for k in sorted(det.keys()):
|
||||||
|
if "env" in k.lower() or k in ("aliases", "is_skipped", "production_branch"):
|
||||||
|
val = det.get(k)
|
||||||
|
s = json.dumps(val, ensure_ascii=False) if not isinstance(val, str) else val
|
||||||
|
print(f" {k}: {s[:240]}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
"""Stage Steam API key + session secret for Pages/Worker secret put (no echo)."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
REFRESH = Path(__file__).resolve().parents[1] / ".refresh"
|
||||||
|
STEAM_TMP = REFRESH / "steam_key.tmp"
|
||||||
|
SESSION_TMP = REFRESH / "session_secret.tmp"
|
||||||
|
|
||||||
|
|
||||||
|
def _write(path: Path, value: str) -> None:
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
path.write_text(value, encoding="utf-8")
|
||||||
|
try:
|
||||||
|
os.chmod(path, 0o600)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
steam = (
|
||||||
|
os.environ.get("STEAM_API_KEY")
|
||||||
|
or os.environ.get("KEYZOO_ASSET_SECRET_WEB_API_KEY")
|
||||||
|
or ""
|
||||||
|
).strip()
|
||||||
|
session = (
|
||||||
|
os.environ.get("SESSION_SECRET")
|
||||||
|
or os.environ.get("KEYZOO_ASSET_SECRET_SESSION_SECRET")
|
||||||
|
or ""
|
||||||
|
).strip()
|
||||||
|
if not steam or not session:
|
||||||
|
print("missing steam key or session secret", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
_write(STEAM_TMP, steam)
|
||||||
|
_write(SESSION_TMP, session)
|
||||||
|
print(
|
||||||
|
f"staged {STEAM_TMP.name} len={len(steam)}; "
|
||||||
|
f"{SESSION_TMP.name} len={len(session)}",
|
||||||
|
flush=True,
|
||||||
|
)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
"""Put STEAM_API_KEY + SESSION_SECRET on climperor-relations Pages (prod+preview).
|
||||||
|
|
||||||
|
Reads secrets from env or staged temp files under web/.refresh/ (no secret echo).
|
||||||
|
Prefers `wrangler pages secret put`; falls back to Pages project PATCH.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ACCT = "510534f7f6284344aadaf2f5a0794d48"
|
||||||
|
PROJ = "climperor-relations"
|
||||||
|
HERE = Path(__file__).resolve().parent
|
||||||
|
FRONTEND = HERE.parents[0] / "frontend"
|
||||||
|
REFRESH = Path(__file__).resolve().parents[1] / ".refresh"
|
||||||
|
STEAM_TMP = REFRESH / "steam_key.tmp"
|
||||||
|
SESSION_TMP = REFRESH / "session_secret.tmp"
|
||||||
|
|
||||||
|
|
||||||
|
def _creds() -> tuple[str, str]:
|
||||||
|
email = os.environ.get("CLOUDFLARE_EMAIL") or os.environ.get(
|
||||||
|
"KEYZOO_ASSET_META_USERNAME"
|
||||||
|
)
|
||||||
|
key = os.environ.get("CLOUDFLARE_API_KEY") or os.environ.get(
|
||||||
|
"KEYZOO_ASSET_SECRET_GLOBAL_API_KEY"
|
||||||
|
)
|
||||||
|
if not email or not key:
|
||||||
|
raise SystemExit("missing Cloudflare credentials")
|
||||||
|
return email, key
|
||||||
|
|
||||||
|
|
||||||
|
def _secret(name: str, env_keys: list[str], staged: Path) -> str:
|
||||||
|
for k in env_keys:
|
||||||
|
v = (os.environ.get(k) or "").strip()
|
||||||
|
if v:
|
||||||
|
return v
|
||||||
|
if staged.is_file():
|
||||||
|
v = staged.read_text(encoding="utf-8").strip()
|
||||||
|
try:
|
||||||
|
staged.unlink()
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
if v:
|
||||||
|
return v
|
||||||
|
raise SystemExit(f"missing {name}")
|
||||||
|
|
||||||
|
|
||||||
|
def cf(method: str, path: str, body: dict | None = None) -> dict:
|
||||||
|
email, key = _creds()
|
||||||
|
data = None if body is None else json.dumps(body).encode("utf-8")
|
||||||
|
req = urllib.request.Request(
|
||||||
|
f"https://api.cloudflare.com/client/v4{path}",
|
||||||
|
data=data,
|
||||||
|
method=method,
|
||||||
|
headers={
|
||||||
|
"X-Auth-Email": email,
|
||||||
|
"X-Auth-Key": key,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"User-Agent": "climperor-put-pages-secrets",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
|
return json.loads(resp.read().decode())
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
raw = e.read().decode("utf-8", errors="replace")
|
||||||
|
raise SystemExit(f"CF API {e.code}: {raw[:500]}") from e
|
||||||
|
|
||||||
|
|
||||||
|
def _wrangler_put(name: str, value: str) -> None:
|
||||||
|
email, key = _creds()
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["CLOUDFLARE_EMAIL"] = email
|
||||||
|
env["CLOUDFLARE_API_KEY"] = key
|
||||||
|
env["CLOUDFLARE_ACCOUNT_ID"] = ACCT
|
||||||
|
env["CI"] = "true"
|
||||||
|
print(f"wrangler pages secret put {name} …", flush=True)
|
||||||
|
cmd = (
|
||||||
|
f'npx --yes wrangler@3 pages secret put {name} '
|
||||||
|
f'--project-name {PROJ}'
|
||||||
|
)
|
||||||
|
proc = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
input=value + "\n",
|
||||||
|
text=True,
|
||||||
|
capture_output=True,
|
||||||
|
env=env,
|
||||||
|
cwd=str(FRONTEND if FRONTEND.is_dir() else HERE),
|
||||||
|
shell=True,
|
||||||
|
)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
print(proc.stderr or proc.stdout, file=sys.stderr)
|
||||||
|
raise SystemExit(proc.returncode)
|
||||||
|
tail = (proc.stdout or "").strip().splitlines()
|
||||||
|
if tail:
|
||||||
|
print(tail[-1], flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
steam = _secret(
|
||||||
|
"STEAM_API_KEY",
|
||||||
|
["STEAM_API_KEY", "KEYZOO_ASSET_SECRET_WEB_API_KEY"],
|
||||||
|
STEAM_TMP,
|
||||||
|
)
|
||||||
|
session = _secret(
|
||||||
|
"SESSION_SECRET",
|
||||||
|
["SESSION_SECRET", "KEYZOO_ASSET_SECRET_SESSION_SECRET"],
|
||||||
|
SESSION_TMP,
|
||||||
|
)
|
||||||
|
# Official path: wrangler pages secret put (production).
|
||||||
|
_wrangler_put("STEAM_API_KEY", steam)
|
||||||
|
_wrangler_put("SESSION_SECRET", session)
|
||||||
|
|
||||||
|
# Also patch preview so *.pages.dev preview deploys work.
|
||||||
|
proj = cf("GET", f"/accounts/{ACCT}/pages/projects/{PROJ}")
|
||||||
|
result = proj.get("result") or {}
|
||||||
|
dc = result.get("deployment_configs") or {}
|
||||||
|
secrets = {
|
||||||
|
"STEAM_API_KEY": {"type": "secret_text", "value": steam},
|
||||||
|
"SESSION_SECRET": {"type": "secret_text", "value": session},
|
||||||
|
}
|
||||||
|
preview_cfg = dc.get("preview") or {}
|
||||||
|
preview_entry: dict = {"env_vars": secrets}
|
||||||
|
wch = preview_cfg.get("wrangler_config_hash")
|
||||||
|
if wch:
|
||||||
|
preview_entry["wrangler_config_hash"] = wch
|
||||||
|
out = cf(
|
||||||
|
"PATCH",
|
||||||
|
f"/accounts/{ACCT}/pages/projects/{PROJ}",
|
||||||
|
{"deployment_configs": {"preview": preview_entry}},
|
||||||
|
)
|
||||||
|
if not out.get("success"):
|
||||||
|
print("preview patch soft-fail", flush=True)
|
||||||
|
|
||||||
|
check = cf("GET", f"/accounts/{ACCT}/pages/projects/{PROJ}")
|
||||||
|
cdc = (check.get("result") or {}).get("deployment_configs") or {}
|
||||||
|
for env_name in ("production", "preview"):
|
||||||
|
ev = ((cdc.get(env_name) or {}).get("env_vars") or {})
|
||||||
|
names = sorted(ev.keys())
|
||||||
|
types = {
|
||||||
|
k: (ev[k] or {}).get("type") for k in ("STEAM_API_KEY", "SESSION_SECRET")
|
||||||
|
}
|
||||||
|
print(f"{env_name}: env_vars={names} secret_types={types}", flush=True)
|
||||||
|
print("ok", flush=True)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in New Issue
Block a user