Initial commit: grok-free-register-oss

Open-source Grok free registration CLI, xai_enroller auth pipeline,
local auth service, tests and docs.
This commit is contained in:
chaos committed 2026-07-16 21:04:05 +08:00
commit d10009d639
72 files changed
+18752

No files matched your search

+140
View File
@@ -0,0 +1,140 @@
#!/usr/bin/env bash
# 快速清历史归档(不碰现网号池)。
#
# 只删这些「历史」:
# keys/cpa_ready/cpa_ready_*.zip 导出包
# keys/cpa_ready/_discarded/** 已丢弃号
# keys/*.bak* / keys/*~ / keys/__pycache__
# keys/async_auth.log / logs/*
# keys/212.zip 等杂包
#
# 默认保留:
# keys/cpa_ready/xai-*.json
# keys/cpa_ready/_state.tsv
# keys/acc.md / accounts.txt / auth-sessions.jsonl
# AUTH_DIR/authenticated 现网出货
#
# 用法(项目根):
# bash scripts/clean_history.sh # dry-run
# bash scripts/clean_history.sh --yes # 真删
# bash scripts/clean_history.sh --yes --deep # 再清 source-snapshot / 旧 .bak 脚本
#
# 全量清零号池请用: bash reset_pipeline.sh --yes
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
KEYS="$ROOT/keys"
CPA="$KEYS/cpa_ready"
AUTH_DIR="${XAI_AUTH_SERVICE_LOCAL_DIR:-${XAI_ENROLLER_LOCAL_AUTH_DIR:-$HOME/Downloads/grok-free-register-auth}}"
LOGS="$ROOT/logs"
YES=0
DEEP=0
for arg in "$@"; do
case "$arg" in
--yes|-y) YES=1 ;;
--deep) DEEP=1 ;;
-h|--help)
sed -n '2,24p' "$0"
exit 0
;;
*)
echo "未知参数: $arg" >&2
exit 2
;;
esac
done
size_of() {
local p="$1"
if [[ -e "$p" ]]; then
du -sh "$p" 2>/dev/null | awk '{print $1}'
else
echo "-"
fi
}
list_targets() {
# zips / archives under cpa_ready and keys
find "$CPA" -maxdepth 1 -type f \( -name 'cpa_ready_*.zip' -o -name '*.zip' -o -name '*.tar' -o -name '*.tar.gz' -o -name '*.tgz' \) 2>/dev/null || true
find "$KEYS" -maxdepth 1 -type f \( -name '*.zip' -o -name '*.tar' -o -name '*.tar.gz' -o -name '*.tgz' \) 2>/dev/null || true
# discarded
if [[ -d "$CPA/_discarded" ]]; then
find "$CPA/_discarded" -mindepth 1 2>/dev/null || true
fi
# logs / pyc / bak
find "$KEYS" -maxdepth 1 -type f \( -name '*.log' -o -name '*.bak' -o -name '*.bak.*' -o -name '*~' \) 2>/dev/null || true
find "$KEYS" -maxdepth 1 -type d -name '__pycache__' 2>/dev/null || true
if [[ -d "$LOGS" ]]; then
find "$LOGS" -type f 2>/dev/null || true
fi
if [[ "$DEEP" -eq 1 ]]; then
# deep: old snapshot growth + script backups; still keep live pool
[[ -f "$AUTH_DIR/source-snapshot.jsonl" ]] && echo "$AUTH_DIR/source-snapshot.jsonl"
find "$KEYS" -maxdepth 1 -type f -name 'acpa_watchdog.py.bak*' 2>/dev/null || true
fi
}
mapfile -t TARGETS < <(list_targets | awk 'NF' | sort -u)
echo "=== grok-free-register 快速清历史 ==="
echo "ROOT = $ROOT"
echo "KEYS = $KEYS"
echo "AUTH_DIR = $AUTH_DIR"
echo "MODE = $([[ $YES -eq 1 ]] && echo APPLY || echo dry-run)$([[ $DEEP -eq 1 ]] && echo ' +deep' || true)"
echo
if [[ "${#TARGETS[@]}" -eq 0 ]]; then
echo "没有可清的历史文件。"
exit 0
fi
echo "-- 将处理 --"
total=0
for f in "${TARGETS[@]}"; do
if [[ -e "$f" ]]; then
echo " $(size_of "$f") $f"
total=$((total + 1))
fi
done
echo " 共 $total 项"
echo
# live pool summary (never touched)
echo "-- 现网号池(保留)--"
echo " cpa xai-*.json = $(find "$CPA" -maxdepth 1 -name 'xai-*.json' -type f 2>/dev/null | wc -l | tr -d ' ')"
echo " acc.md lines = $(wc -l < "$KEYS/acc.md" 2>/dev/null | tr -d ' ' || echo 0)"
echo " sessions = $(wc -l < "$KEYS/auth-sessions.jsonl" 2>/dev/null | tr -d ' ' || echo 0)"
echo " authenticated = $(find "$AUTH_DIR/authenticated" -type f 2>/dev/null | wc -l | tr -d ' ' || echo 0)"
echo
if [[ "$YES" -ne 1 ]]; then
echo "dry-run。确认后: bash scripts/clean_history.sh --yes"
echo "更深一层(含 snapshot): bash scripts/clean_history.sh --yes --deep"
exit 0
fi
echo "-- 执行删除 --"
for f in "${TARGETS[@]}"; do
if [[ -d "$f" ]]; then
rm -rf -- "$f"
echo " removed dir $f"
elif [[ -e "$f" ]]; then
rm -f -- "$f"
echo " removed file $f"
fi
done
# keep empty discarded dir
mkdir -p "$CPA/_discarded" 2>/dev/null || true
mkdir -p "$LOGS" 2>/dev/null || true
echo
echo "-- 清后 --"
echo " cpa zip left = $(find "$CPA" -maxdepth 1 -name '*.zip' -type f 2>/dev/null | wc -l | tr -d ' ')"
echo " discarded files= $(find "$CPA/_discarded" -type f 2>/dev/null | wc -l | tr -d ' ')"
echo " cpa xai-*.json = $(find "$CPA" -maxdepth 1 -name 'xai-*.json' -type f 2>/dev/null | wc -l | tr -d ' ')"
echo " keys size = $(size_of "$KEYS")"
echo "完成。"
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
ensure_runtime() {
local lock_dir=".setup.lock"
local acquired=0
local attempt
for attempt in {1..300}; do
if mkdir "$lock_dir" 2>/dev/null; then
acquired=1
break
fi
sleep 0.2
done
if [ "$acquired" -ne 1 ]; then
echo "[!] 另一个安装进程长时间未结束,请稍后重试。" >&2
return 1
fi
trap 'rmdir .setup.lock 2>/dev/null || true' EXIT INT TERM
if [ ! -d .venv ]; then
echo "[*] 首次运行,安装依赖..."
if ! bash setup.sh; then
rmdir "$lock_dir"
trap - EXIT INT TERM
return 1
fi
fi
rmdir "$lock_dir"
trap - EXIT INT TERM
}
+206
View File
@@ -0,0 +1,206 @@
#!/usr/bin/env python3
"""通过 SSH 导出不含密码的注册会话;兼容历史裸 SSO 记录。"""
import argparse
import json
import os
import sys
import time
from pathlib import Path
COOKIE_FIELDS = frozenset(
{
"name",
"value",
"url",
"domain",
"path",
"expires",
"httpOnly",
"secure",
"sameSite",
}
)
MAX_RECORD_BYTES = 256 * 1024
LEGACY_COOKIE_SCOPE = {
"name": "sso",
"domain": "accounts.x.ai",
"path": "/",
"secure": True,
"httpOnly": True,
"sameSite": "Lax",
}
def _decode_json_line(raw, label):
if len(raw) > MAX_RECORD_BYTES:
raise ValueError(f"invalid {label} record")
try:
document = json.loads(raw.decode("utf-8"))
email = document["email"]
cookies = document["cookies"]
except (UnicodeDecodeError, TypeError, ValueError, KeyError) as exc:
raise ValueError(f"invalid {label} record") from exc
if not isinstance(email, str) or not email or not isinstance(cookies, list) or not cookies:
raise ValueError(f"invalid {label} record")
try:
email.encode("utf-8")
except UnicodeEncodeError as exc:
raise ValueError(f"invalid {label} record") from exc
normalized = []
for cookie in cookies:
if not isinstance(cookie, dict):
raise ValueError(f"invalid {label} record")
filtered = {key: cookie[key] for key in COOKIE_FIELDS if key in cookie}
if not all(
isinstance(filtered.get(key), str) and filtered[key]
for key in ("name", "value")
):
raise ValueError(f"invalid {label} record")
scope = filtered.get("domain") or filtered.get("url")
if not isinstance(scope, str) or not scope:
raise ValueError(f"invalid {label} record")
try:
filtered["name"].encode("utf-8")
filtered["value"].encode("utf-8")
scope.encode("utf-8")
except UnicodeEncodeError as exc:
raise ValueError(f"invalid {label} record") from exc
normalized.append(filtered)
return {"email": email, "cookies": normalized}
def _complete_lines(data):
"""Return newline-terminated records and the unconsumed trailing bytes."""
parts = data.split(b"\n")
return parts[:-1], parts[-1]
def _read_complete_file(path):
if not path.exists():
return []
lines, _incomplete = _complete_lines(path.read_bytes())
return [line for line in lines if line]
def load_snapshots(path, *, raw_lines=None):
snapshots = {}
scopes = {}
lines = _read_complete_file(path) if raw_lines is None else raw_lines
for line in lines:
document = _decode_json_line(line, "session")
email = document["email"]
cookies = document["cookies"]
if email in snapshots:
continue
snapshots[email] = {"email": email, "cookies": cookies}
for cookie in cookies:
name = cookie.get("name")
domain = cookie.get("domain")
if name in {"sso", "sso-rw"} and domain:
scopes[(name, domain)] = {
"name": name,
"domain": domain,
"path": cookie.get("path", "/"),
"secure": bool(cookie.get("secure", True)),
"httpOnly": bool(cookie.get("httpOnly", True)),
"sameSite": cookie.get("sameSite", "Lax"),
}
return snapshots, scopes
def export_sessions(sessions_path, accounts_path, *, session_lines=None):
snapshots, scopes = load_snapshots(sessions_path, raw_lines=session_lines)
for document in snapshots.values():
yield document
if not accounts_path.exists():
return
legacy_scopes = list(scopes.values()) or [LEGACY_COOKIE_SCOPE]
for raw in _read_complete_file(accounts_path):
try:
email, _password, sso = raw.decode("utf-8").rsplit(":", 2)
except (UnicodeDecodeError, ValueError) as exc:
raise ValueError("invalid account record") from exc
if not email or not sso or email in snapshots:
continue
cookies = [{**scope, "value": sso} for scope in legacy_scopes]
yield {"email": email, "cookies": cookies}
def _write_document(document):
payload = json.dumps(document, separators=(",", ":"))
if len(payload.encode("utf-8")) > MAX_RECORD_BYTES:
raise ValueError("invalid session record")
print(payload, flush=False)
def export_and_follow(sessions_path, accounts_path, *, poll_seconds=0.25):
"""Emit a complete snapshot, then losslessly follow the same JSONL fd."""
sessions_path.parent.mkdir(parents=True, exist_ok=True)
if not sessions_path.exists():
fd = os.open(sessions_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
os.close(fd)
os.chmod(sessions_path, 0o600)
stream = sessions_path.open("rb")
with stream:
opened = os.fstat(stream.fileno())
initial_lines, pending = _complete_lines(stream.read())
if len(pending) > MAX_RECORD_BYTES:
raise ValueError("invalid session record")
for document in export_sessions(
sessions_path,
accounts_path,
session_lines=[line for line in initial_lines if line],
):
_write_document(document)
sys.stdout.flush()
while True:
chunk = stream.read()
if chunk:
complete, pending = _complete_lines(pending + chunk)
if len(pending) > MAX_RECORD_BYTES:
raise ValueError("invalid session record")
for raw in complete:
if raw:
_write_document(_decode_json_line(raw, "session"))
sys.stdout.flush()
continue
try:
current = sessions_path.stat()
except FileNotFoundError:
return 3
if (
current.st_dev != opened.st_dev
or current.st_ino != opened.st_ino
or current.st_size < stream.tell()
):
return 3
time.sleep(poll_seconds)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--follow", action="store_true")
parser.add_argument("sessions_path", type=Path)
parser.add_argument("accounts_path", type=Path)
args = parser.parse_args()
try:
if args.follow:
raise SystemExit(export_and_follow(args.sessions_path, args.accounts_path))
for document in export_sessions(args.sessions_path, args.accounts_path):
_write_document(document)
except ValueError:
raise SystemExit(4) from None
except BrokenPipeError:
try:
sys.stdout.close()
finally:
raise SystemExit(0)
if __name__ == "__main__":
main()
+153
View File
@@ -0,0 +1,153 @@
#!/usr/bin/env bash
# 本机注册 → 推 SSO 源到远端 auth 机(local 模式吃 keys/)
#
# 动态公网 IP 不适合「远端 SSH 拉本机」;改由本机主动推:
# keys/auth-sessions.jsonl
# keys/accounts.txt
# → AUTH_HOST:REMOTE_ROOT/keys/
#
# 用法(本机项目根或任意 cwd):
# bash scripts/push_keys_to_auth.sh # 推一次
# bash scripts/push_keys_to_auth.sh --watch # 常驻,文件变了再推
# bash scripts/push_keys_to_auth.sh --interval 15
#
# 环境变量(可写本机 .env,本脚本会 source):
# AUTH_SSH_HOST=user@auth-server.example
# AUTH_REMOTE_ROOT=/opt/grok-free-register
# AUTH_SSH_IDENTITY= # 可选 -i 路径
# AUTH_PUSH_INTERVAL=20 # --watch 轮询秒
#
# 无内置默认主机/路径:必须显式设置 AUTH_SSH_HOST 与 AUTH_REMOTE_ROOT。
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
# 调用方已 export 的 AUTH_* 优先(.env 不得盖掉),方便并行推多台 auth
_PRE_AUTH_SSH_HOST="${AUTH_SSH_HOST-}"
_PRE_AUTH_REMOTE_ROOT="${AUTH_REMOTE_ROOT-}"
_PRE_AUTH_SSH_IDENTITY="${AUTH_SSH_IDENTITY-}"
_PRE_AUTH_PUSH_INTERVAL="${AUTH_PUSH_INTERVAL-}"
if [[ -f "$ROOT/.env" ]]; then
set -a
# shellcheck disable=SC1091
. "$ROOT/.env"
set +a
fi
AUTH_SSH_HOST="${_PRE_AUTH_SSH_HOST:-${AUTH_SSH_HOST:-}}"
AUTH_REMOTE_ROOT="${_PRE_AUTH_REMOTE_ROOT:-${AUTH_REMOTE_ROOT:-}}"
AUTH_SSH_IDENTITY="${_PRE_AUTH_SSH_IDENTITY:-${AUTH_SSH_IDENTITY:-}}"
AUTH_PUSH_INTERVAL="${_PRE_AUTH_PUSH_INTERVAL:-${AUTH_PUSH_INTERVAL:-20}}"
if [[ -z "$AUTH_SSH_HOST" || -z "$AUTH_REMOTE_ROOT" ]]; then
echo "push_keys_to_auth: set AUTH_SSH_HOST and AUTH_REMOTE_ROOT (env or .env)" >&2
echo " e.g. AUTH_SSH_HOST=user@auth-server.example AUTH_REMOTE_ROOT=/opt/grok-free-register" >&2
exit 2
fi
WATCH=0
INTERVAL="$AUTH_PUSH_INTERVAL"
for a in "$@"; do
case "$a" in
--watch) WATCH=1 ;;
--interval)
# next arg handled below if present as --interval=N form preferred
;;
--interval=*) INTERVAL="${a#--interval=}" ;;
-h|--help)
sed -n '2,22p' "$0"
exit 0
;;
esac
done
# support: --interval 15
prev=""
for a in "$@"; do
if [[ "$prev" == "--interval" ]]; then
INTERVAL="$a"
fi
prev="$a"
done
SSH_OPTS=(-o BatchMode=yes -o ConnectTimeout=15 -o ServerAliveInterval=15)
if [[ -n "$AUTH_SSH_IDENTITY" ]]; then
SSH_OPTS+=(-i "$AUTH_SSH_IDENTITY")
fi
KEYS_SRC="$ROOT/keys"
REMOTE_KEYS="${AUTH_REMOTE_ROOT}/keys"
# 只推 auth 源,不推 cpa_ready/acc(远端自己整备)
FILES=(auth-sessions.jsonl accounts.txt)
log() { printf '[push_keys %s] %s\n' "$(date +%H:%M:%S)" "$*"; }
fingerprint() {
local f
for f in "${FILES[@]}"; do
if [[ -f "$KEYS_SRC/$f" ]]; then
# size + mtime + sha256 head — cheap change detect
stat -c '%s %Y' "$KEYS_SRC/$f" 2>/dev/null || stat -f '%z %m' "$KEYS_SRC/$f"
sha256sum "$KEYS_SRC/$f" 2>/dev/null | awk '{print $1}'
else
echo "missing:$f"
fi
done
}
push_once() {
local missing=0 f
for f in "${FILES[@]}"; do
if [[ ! -f "$KEYS_SRC/$f" ]]; then
log "⚠ 缺 $KEYS_SRC/$f"
missing=1
fi
done
if [[ "$missing" -eq 1 ]]; then
return 1
fi
# 远端原子替换:先推 .push.tmp 再 mv
ssh "${SSH_OPTS[@]}" "$AUTH_SSH_HOST" "mkdir -p $(printf %q "$REMOTE_KEYS") && chmod 700 $(printf %q "$REMOTE_KEYS") 2>/dev/null || true"
local remote_tmp remote_final
for f in "${FILES[@]}"; do
remote_tmp="${REMOTE_KEYS}/.${f}.push.tmp"
remote_final="${REMOTE_KEYS}/${f}"
# rsync over ssh when available; fallback scp
if command -v rsync >/dev/null 2>&1; then
rsync -az -e "ssh ${SSH_OPTS[*]}" \
"$KEYS_SRC/$f" \
"${AUTH_SSH_HOST}:${remote_tmp}"
else
scp "${SSH_OPTS[@]}" "$KEYS_SRC/$f" "${AUTH_SSH_HOST}:${remote_tmp}"
fi
ssh "${SSH_OPTS[@]}" "$AUTH_SSH_HOST" \
"chmod 600 $(printf %q "$remote_tmp") && mv -f $(printf %q "$remote_tmp") $(printf %q "$remote_final")"
done
local n_sess n_acc
n_sess=$(wc -l < "$KEYS_SRC/auth-sessions.jsonl" | tr -d ' ')
n_acc=$(wc -l < "$KEYS_SRC/accounts.txt" | tr -d ' ')
log "→ ${AUTH_SSH_HOST}:${REMOTE_KEYS}/ sessions=${n_sess} accounts=${n_acc}"
}
LAST_FP=""
if [[ "$WATCH" -eq 0 ]]; then
push_once
exit $?
fi
log "watch host=${AUTH_SSH_HOST} root=${AUTH_REMOTE_ROOT} interval=${INTERVAL}s"
while true; do
FP="$(fingerprint)"
if [[ "$FP" != "$LAST_FP" ]]; then
if push_once; then
LAST_FP="$FP"
else
log "推送失败,${INTERVAL}s 后重试"
fi
fi
sleep "$INTERVAL"
done