Add Gmail+Email Worker hybrid mail path and private runtime config
Prefer Cloudflare Worker KV for verification codes with optional short IMAP fallback disabled for private deploy; track config.json/.env for private repo. Also add worker project, protocol mint backoff, and fail-fast Turnstile/mail timeouts from batch runs.
This commit is contained in:
1 parent
187792d6be
commit
55f56d27c9
19 files changed
+1225
-85
No files matched your search
Executable
+52
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
# Helper: create KV + print next steps for Email Worker
|
||||
set -euo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT/cf-email-worker"
|
||||
|
||||
if ! command -v npx >/dev/null 2>&1; then
|
||||
echo "需要 Node.js / npx。先安装: https://nodejs.org/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[1/4] wrangler login (如已登录可跳过)"
|
||||
npx wrangler login || true
|
||||
|
||||
echo "[2/4] 创建 KV namespace GROK_MAIL_CODES"
|
||||
OUT=$(npx wrangler kv namespace create GROK_MAIL_CODES 2>&1 || true)
|
||||
echo "$OUT"
|
||||
ID=$(echo "$OUT" | sed -n 's/.*id *= *"\([^"]*\)".*/\1/p' | head -1)
|
||||
if [[ -z "${ID}" ]]; then
|
||||
ID=$(echo "$OUT" | grep -oE '[a-f0-9]{32}' | head -1 || true)
|
||||
fi
|
||||
if [[ -n "${ID}" ]]; then
|
||||
echo "检测到 KV id=$ID — 写入 wrangler.toml"
|
||||
if grep -q 'REPLACE_WITH_KV_NAMESPACE_ID' wrangler.toml; then
|
||||
sed -i "s/REPLACE_WITH_KV_NAMESPACE_ID/${ID}/g" wrangler.toml
|
||||
fi
|
||||
else
|
||||
echo "未能自动解析 KV id,请手动填 wrangler.toml"
|
||||
fi
|
||||
|
||||
echo "[3/4] 设置 CODE_API_KEY secret"
|
||||
echo "将提示你输入密钥(与 config gmail_worker_api_key 一致)"
|
||||
npx wrangler secret put CODE_API_KEY
|
||||
|
||||
if [[ -n "${GMAIL_FORWARD_TO:-}" ]]; then
|
||||
echo "[3b] 设置 GMAIL_FORWARD_TO=$GMAIL_FORWARD_TO"
|
||||
printf '%s' "$GMAIL_FORWARD_TO" | npx wrangler secret put GMAIL_FORWARD_TO
|
||||
else
|
||||
echo "[3b] 跳过 GMAIL_FORWARD_TO(可 export GMAIL_FORWARD_TO=you@gmail.com 后重跑)"
|
||||
fi
|
||||
|
||||
echo "[4/4] deploy"
|
||||
npx wrangler deploy
|
||||
|
||||
echo
|
||||
echo "完成。接下来:"
|
||||
echo " 1) Email Routing catch-all → 绑定本 Worker"
|
||||
echo " 2) config.json 设置:"
|
||||
echo " gmail_worker_enabled=true"
|
||||
echo " gmail_worker_url=https://<worker>.workers.dev"
|
||||
echo " gmail_worker_api_key=<你的 CODE_API_KEY>"
|
||||
echo " 3) 测试: python scripts/test_gmail_worker.py --to test@yourdomain.com"
|
||||
Executable
+82
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test Cloudflare Email Worker code API (and optional local hybrid path)."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlencode
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def load_cfg():
|
||||
p = ROOT / "config.json"
|
||||
if p.is_file():
|
||||
return json.loads(p.read_text(encoding="utf-8"))
|
||||
return {}
|
||||
|
||||
|
||||
def main():
|
||||
cfg = load_cfg()
|
||||
ap = argparse.ArgumentParser(description="Test gmail Email Worker /code API")
|
||||
ap.add_argument("--url", default=cfg.get("gmail_worker_url") or os.getenv("GMAIL_WORKER_URL") or "")
|
||||
ap.add_argument("--key", default=cfg.get("gmail_worker_api_key") or os.getenv("GMAIL_WORKER_API_KEY") or "")
|
||||
ap.add_argument("--to", required=True, help="alias@domain to query")
|
||||
ap.add_argument("--timeout", type=float, default=20)
|
||||
ap.add_argument("--interval", type=float, default=0.5)
|
||||
ap.add_argument("--ingest-demo", action="store_true", help="POST a fake code then GET it")
|
||||
ap.add_argument("--no-consume", action="store_true")
|
||||
args = ap.parse_args()
|
||||
base = (args.url or "").rstrip("/")
|
||||
key = args.key or ""
|
||||
if not base or not key:
|
||||
print("需要 --url/--key 或 config.json 的 gmail_worker_url / gmail_worker_api_key")
|
||||
return 1
|
||||
|
||||
def get(path, params=None):
|
||||
q = dict(params or {})
|
||||
q["key"] = key
|
||||
url = f"{base}{path}?{urlencode(q)}"
|
||||
req = Request(url, headers={"x-api-key": key})
|
||||
with urlopen(req, timeout=10) as r:
|
||||
return r.status, json.loads(r.read().decode())
|
||||
|
||||
def post(path, body):
|
||||
url = f"{base}{path}?{urlencode({'key': key})}"
|
||||
data = json.dumps(body).encode()
|
||||
req = Request(url, data=data, headers={"content-type": "application/json", "x-api-key": key}, method="POST")
|
||||
with urlopen(req, timeout=10) as r:
|
||||
return r.status, json.loads(r.read().decode())
|
||||
|
||||
st, health = get("/health")
|
||||
print("health", st, health)
|
||||
|
||||
if args.ingest_demo:
|
||||
code = "AB1-CD2"
|
||||
st, body = post("/ingest", {"to": args.to, "code": code, "subject": f"{code} xAI confirmation code"})
|
||||
print("ingest", st, body)
|
||||
|
||||
deadline = time.time() + args.timeout
|
||||
while time.time() < deadline:
|
||||
try:
|
||||
st, body = get(
|
||||
"/code",
|
||||
{"to": args.to.lower(), "consume": "0" if args.no_consume else "1"},
|
||||
)
|
||||
print("code", st, body)
|
||||
if st == 200 and body.get("code"):
|
||||
return 0
|
||||
except Exception as e:
|
||||
print("wait", e)
|
||||
time.sleep(args.interval)
|
||||
print("timeout — 若是真实邮件,检查 Email Routing 是否绑到 Worker")
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user