#!/usr/bin/env python3
"""git credential helper：向 chv-authd 按次取凭证（feat-chv-muse-p3）。

git credential 协议：git 把 `protocol/host/path`（每行 `key=value`，
空行结束）写进本进程 stdin；本进程向 authd 的 Unix socket 发
一次 JSON 请求，拿到用户名/口令后按协议回给 git（属性 + 空行）。

口令只进 git 子进程内存：本进程不写文件、不打日志（拒绝时只
报 kind，不含值）；`password_expiry_utc` 按释放的 expires_at
回给 git，git 在该时间后重新问 helper（不缓存过期凭证）。
authd 拒绝 / socket 不通时本进程 exit 1——git 操作随之失败
（fail-closed，绝不回落成“无凭证也照推”）。

用法（chv-sync boot 配好后 git 自动调，不用手工跑）：
  git config --global credential.helper /usr/local/bin/git-credential-chv-authd
  git config --global credential.useHttpPath true
（后者不可少：git 默认不把 path 发给 helper，没有 path 本进程
无法推断仓库与操作，会直接拒绝。）
socket 路径可用 CHV_AUTHD_SOCKET 覆盖（测试用 /tmp 下的假 socket）。
"""

import json
import os
import socket
import sys

DEFAULT_SOCKET_PATH = "/run/chv-authd/authd.sock"
SOCKET_TIMEOUT_SECONDS = 5


def _read_credential_request():
    """读 git credential 协议的请求属性（空行结束）→ dict。"""
    attrs = {}
    for line in sys.stdin:
        line = line.rstrip("\n")
        if not line:
            break
        if "=" in line:
            key, value = line.split("=", 1)
            attrs[key.strip()] = value.strip()
    return attrs


def _ask_authd(socket_path, host, path):
    """向 authd 要一次凭证：返回 (username, password, expires_at)。
    拒绝/通信失败即 RuntimeError（只含 kind，不含值）。"""
    req = {"connector": "gitea", "host": host, "path": path}
    sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    sock.settimeout(SOCKET_TIMEOUT_SECONDS)
    try:
        sock.connect(socket_path)
        sock.sendall((json.dumps(req) + "\n").encode("utf-8"))
        chunks = []
        while True:
            data = sock.recv(65536)
            if not data:
                break
            chunks.append(data)
            if b"\n" in data:
                break
    except OSError as exc:
        raise RuntimeError("authd_unreachable:%s" % exc)
    finally:
        sock.close()
    try:
        resp = json.loads(b"".join(chunks).decode("utf-8"))
    except ValueError:
        raise RuntimeError("authd_bad_response")
    if not resp.get("ok"):
        raise RuntimeError("authd_refused:%s"
                           % resp.get("error", "refused"))
    return (resp["username"], resp["password"],
            resp.get("expires_at"))


def main(argv=None):
    """helper 入口：读协议 → 问 authd → 回属性。拒绝即 exit 1。"""
    attrs = _read_credential_request()
    host = attrs.get("host", "")
    path = attrs.get("path", "")
    if attrs.get("protocol") != "https" or not host or not path:
        print("git-credential-chv-authd: 请求不完整（须 protocol=https "
              "+ host + path），拒绝", file=sys.stderr)
        return 1
    if not path.startswith("/"):
        path = "/" + path
    # git 的 info/refs 路径带 ?service=... 查询串：Sentinel 的
    # L7 规则按干净路径匹配，这里先去掉查询串再问 authd。
    path = path.split("?", 1)[0]
    socket_path = os.environ.get("CHV_AUTHD_SOCKET") \
        or DEFAULT_SOCKET_PATH
    try:
        username, password, expires_at = _ask_authd(
            socket_path, host, path)
    except RuntimeError as exc:
        print("git-credential-chv-authd: %s" % exc, file=sys.stderr)
        return 1
    print("username=%s" % username)
    print("password=%s" % password)
    if expires_at:
        print("password_expiry_utc=%d" % int(expires_at))
    print()
    return 0


if __name__ == "__main__":
    sys.exit(main())
