From 1d7328e7c77da2681b7bc4a2c4ef91ad93167e3a Mon Sep 17 00:00:00 2001 From: SHAREit Date: Fri, 16 Oct 2026 15:36:53 +0800 Subject: [PATCH] fix: use env credentials for pipeline push --- .gitignore | 1 + README.md | 18 +++--- .../com.roasbloom.af-click-gap.plist.template | 3 +- scripts/run_pipeline.sh | 59 +++++++++++++++++++ 4 files changed, 72 insertions(+), 9 deletions(-) create mode 100755 scripts/run_pipeline.sh diff --git a/.gitignore b/.gitignore index dd2e24c..514c05c 100755 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ *.sqlite *.sqlite3 *.log +*.tmp __pycache__/ *.py[cod] .cache/ diff --git a/README.md b/README.md index 3f18462..cac4475 100755 --- a/README.md +++ b/README.md @@ -23,16 +23,18 @@ clicks_diff_rate REAL, updated_at TEXT NOT NULL ## 手工运行 +mac2 推荐始终通过包装脚本运行: + ```sh cd /Users/shareit./Library/CloudStorage/OneDrive-个人/ubuntu/Gitea/af-click-gap -python3 -m py_compile scripts/af_click_gap_pipeline.py -python3 scripts/af_click_gap_pipeline.py --help -python3 scripts/af_click_gap_pipeline.py --dry-run -python3 scripts/af_click_gap_pipeline.py --push # 写 JSON,commit 并 push -python3 scripts/af_click_gap_pipeline.py --no-push # 写 JSON 和 commit,不 push -python3 scripts/af_click_gap_pipeline.py --start-date 2026-10-10 --end-date 2026-10-16 --no-push +scripts/run_pipeline.sh +scripts/run_pipeline.sh --start-date 2026-10-10 --end-date 2026-10-16 ``` +包装脚本默认定位项目根目录,并读取 sibling 项目 `../direct-report-dashboard/.env`(mac2 默认路径为 `~/ubuntu/Gitea/af-click-gap` 与 `~/ubuntu/Gitea/direct-report-dashboard`)。也可以用 `PROJECT_ROOT` 和 `ENV_FILE` 覆盖路径。它只解析 `AGENTSTAGE_USER`、`AGENTSTAGE_TOKEN`,不执行整个 `.env`;凭据只进入子进程环境,不写入文件、remote URL、命令行参数或日志。脚本使用临时 `GIT_ASKPASS` 响应 Git 的用户名和密码提示,临时文件权限为仅当前用户可读写,并在退出时清理。 + +不要直接运行 `python3 scripts/af_click_gap_pipeline.py --push`:该命令可能使用 mac2 Keychain 中没有仓库写权限的旧凭据。包装脚本会执行相同的 SQLite 最近 7 天提取和 `--push`,额外参数会继续传给流水线。需要只检查数据时可直接使用 `python3 scripts/af_click_gap_pipeline.py --dry-run`。 + `--dry-run` 只读取数据库并在仓库外临时目录生成、校验 JSON,不写 `data` 或 Git。正式运行先原子替换 `data/` 输出目录中的数据文件,再执行 Git;数据库查询或校验失败不会发布文件。Git 安全检查只允许本次生成的 `data/*.json`(含 `data/index.json`),工作区存在其它未提交改动时拒绝继续。push 失败会保留本地 commit,不会 force push。 ## 看板 @@ -41,7 +43,7 @@ python3 scripts/af_click_gap_pipeline.py --start-date 2026-10-10 --end-date 2026 ## mac2 launchd -模板文件为 `launchd/com.roasbloom.af-click-gap.plist.template`,必须在 mac2 使用实际 Python 路径和项目路径渲染后安装: +launchd 会调用 `scripts/run_pipeline.sh`,因此也使用 sibling 项目 `.env` 中的凭据;模板通过 `PYTHON_BIN` 传入 mac2 的 Python 路径,并保留工作目录和 stdout/stderr 日志路径: ```sh cd /Users/shareit./Library/CloudStorage/OneDrive-个人/ubuntu/Gitea/af-click-gap @@ -57,7 +59,7 @@ launchctl bootstrap "gui/$(id -u)" "$HOME/Library/LaunchAgents/com.roasbloom.af- ## Git 创建和推送 -远程仓库约定为 Gitea `https://git.ops.roasbloom.site/liuji/af-click-gap.git`。创建前应检查仓库是否已存在,确认不存在后使用 macOS Keychain 中已有的 Git 凭证创建,再在本地配置 `origin` 并推送当前 `main` 分支。认证信息只通过现有 credential helper 使用,不写入本项目或命令输出。 +远程仓库约定为 Gitea `https://git.ops.roasbloom.site/liuji/af-click-gap.git`。仓库创建和首次配置可使用已有流程;日常 mac2 发布必须使用 `scripts/run_pipeline.sh`,它从 `/Users/shareit./Library/CloudStorage/OneDrive-个人/ubuntu/Gitea/direct-report-dashboard/.env`(或 `ENV_FILE` 指定路径)读取 `AGENTSTAGE_USER` 和 `AGENTSTAGE_TOKEN`。凭据不进入 Git、remote URL、命令行、临时 askpass 文件或日志,且 `.env` 绝不提交。 ## 验证和安全 diff --git a/launchd/com.roasbloom.af-click-gap.plist.template b/launchd/com.roasbloom.af-click-gap.plist.template index 1299beb..64ac7d9 100755 --- a/launchd/com.roasbloom.af-click-gap.plist.template +++ b/launchd/com.roasbloom.af-click-gap.plist.template @@ -3,7 +3,8 @@ Labelcom.roasbloom.af-click-gap -ProgramArguments__MAC2_PYTHON3____PROJECT_DIR__/scripts/af_click_gap_pipeline.py--push +ProgramArguments__PROJECT_DIR__/scripts/run_pipeline.sh +EnvironmentVariablesPYTHON_BIN__MAC2_PYTHON3__ WorkingDirectory__PROJECT_DIR__ StartCalendarIntervalHour2Minute0 StandardOutPath__LOG_DIR__/pipeline.stdout.logStandardErrorPath__LOG_DIR__/pipeline.stderr.log diff --git a/scripts/run_pipeline.sh b/scripts/run_pipeline.sh new file mode 100755 index 0000000..b13209e --- /dev/null +++ b/scripts/run_pipeline.sh @@ -0,0 +1,59 @@ +#!/bin/sh +# Run the SQLite pipeline with the repository's dedicated Gitea credential. +set -eu + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +PROJECT_ROOT=${PROJECT_ROOT:-$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)} +ENV_FILE=${ENV_FILE:-$(CDPATH= cd -- "$PROJECT_ROOT/../direct-report-dashboard" && pwd)/.env} +PYTHON_BIN=${PYTHON_BIN:-python3} + +if [ ! -r "$ENV_FILE" ]; then + printf '%s\n' "Credential file is not readable: $ENV_FILE" >&2 + exit 1 +fi + +# Read only the two Git credential variables; do not source the .env file. +dotenv_value() { + awk -v wanted="$1" ' + $0 ~ "^[[:space:]]*" wanted "[[:space:]]*=" { + sub("^[[:space:]]*" wanted "[[:space:]]*=", "") + if ($0 ~ /^".*"[[:space:]]*$/) { + sub(/^"/, ""); sub(/"[[:space:]]*$/, "") + } else if ($0 ~ /^'"'"'.*'"'"'[[:space:]]*$/) { + sub(/^'"'"'/, ""); sub(/'"'"'[[:space:]]*$/, "") + } + print + exit + } + ' "$ENV_FILE" +} + +AGENTSTAGE_USER=$(dotenv_value AGENTSTAGE_USER) +AGENTSTAGE_TOKEN=$(dotenv_value AGENTSTAGE_TOKEN) +if [ -z "$AGENTSTAGE_USER" ] || [ -z "$AGENTSTAGE_TOKEN" ]; then + printf '%s\n' "AGENTSTAGE_USER and AGENTSTAGE_TOKEN are required in $ENV_FILE" >&2 + exit 1 +fi +export AGENTSTAGE_USER AGENTSTAGE_TOKEN + +umask 077 +ASKPASS=$(mktemp "${TMPDIR:-/tmp}/af-click-gap-askpass.XXXXXX") +cleanup() { + rm -f -- "$ASKPASS" +} +trap cleanup EXIT HUP INT TERM + +cat >"$ASKPASS" <<'ASKPASS_SCRIPT' +#!/bin/sh +case "$1" in + *[Uu]sername*) printf '%s\n' "$AGENTSTAGE_USER" ;; + *[Pp]assword*) printf '%s\n' "$AGENTSTAGE_TOKEN" ;; + *) exit 1 ;; +esac +ASKPASS_SCRIPT +chmod 700 "$ASKPASS" + +export GIT_ASKPASS="$ASKPASS" +export GIT_TERMINAL_PROMPT=0 +"$PYTHON_BIN" "$PROJECT_ROOT/scripts/af_click_gap_pipeline.py" --push "$@" +exit $?