feat: AgentCheckin v1.2.2 — fnOS 应用:TRAE/WorkBuddy/Qoder 三平台每日积分自动签到
- 原生 fnOS 应用(CGI 入口 + Python 守护进程),依赖 python312 运行时 - 三平台签到:TRAE 每日 200 积分、WorkBuddy 7 天循环、Qoder 每日 100 Credits - WorkBuddy 网页授权(OAuth 设备流)、TRAE icubeAuth 解密 Token、Qoder Cosy 设备头 - 多时段调度(默认 00:05/09:00/12:30/19:00)、启动补跑、幂等签到+回查确认 - 多平台 Webhook 通知(企业微信/钉钉/飞书/Bark/Server酱/PushPlus 自动识别) - Qoder 双站点 Token 隔离自动探测与修复 - 前端:仪表盘/账号管理/签到记录/设置四个页签,30s 轮询+变化检测+隐藏暂停 - 测试:34 后端 + 25 E2E + 9 前端回归 + 真实时钟调度测试
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
#!/bin/bash
|
||||
# AgentCheckin CGI entry for fnOS.
|
||||
# - serves static files from the app's www directory
|
||||
# - /api/* requests are dispatched to the Python backend (api.py)
|
||||
#
|
||||
# Installed layout (see fnOS app framework docs):
|
||||
# /var/apps/AgentCheckin/target/ui/index.cgi (this file)
|
||||
# /var/apps/AgentCheckin/target/app/backend (python backend)
|
||||
# /var/apps/AgentCheckin/target/app/www (static files)
|
||||
# /vol{n}/@appdata/AgentCheckin (TRIM_PKGVAR: persistent data)
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd)"
|
||||
APP_ROOT="${SCRIPT_DIR}/.."
|
||||
BASE_PATH="${APP_ROOT}/www"
|
||||
BACKEND_DIR="${APP_ROOT}/backend"
|
||||
|
||||
# fnOS CGI processes carry no TRIM_PKGVAR. Resolve the persistent data dir
|
||||
# (@appdata/AgentCheckin) and export it explicitly so the Python backend and
|
||||
# the scheduler daemon always agree on one location.
|
||||
if [ -n "${TRIM_PKGVAR}" ] && [ -d "${TRIM_PKGVAR}" ]; then
|
||||
export AGENTCHECKIN_DATA_DIR="${TRIM_PKGVAR}"
|
||||
else
|
||||
for _cand in \
|
||||
"${APP_ROOT}/../../@appdata/AgentCheckin" \
|
||||
"/var/apps/AgentCheckin/var" \
|
||||
"/vol1/@appdata/AgentCheckin" \
|
||||
"/vol2/@appdata/AgentCheckin"; do
|
||||
if [ -d "$_cand" ]; then
|
||||
export AGENTCHECKIN_DATA_DIR="$_cand"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Resolve a python3 interpreter: prefer the fnOS runtime dependency app.
|
||||
PYBIN=""
|
||||
for candidate in \
|
||||
/var/apps/python312/target/bin/python3 \
|
||||
/var/apps/python311/target/bin/python3 \
|
||||
/var/apps/python310/target/bin/python3 \
|
||||
/usr/bin/python3 \
|
||||
/usr/local/bin/python3; do
|
||||
if [ -x "$candidate" ]; then PYBIN="$candidate"; break; fi
|
||||
done
|
||||
|
||||
json_error() {
|
||||
# $1 = http status body, $2 = message
|
||||
printf 'Status: %s\r\n' "$1"
|
||||
printf 'Content-Type: application/json; charset=utf-8\r\n'
|
||||
printf 'Cache-Control: no-store\r\n'
|
||||
printf '\r\n'
|
||||
printf '{"error":"%s"}\n' "$2"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Parse the relative request path (everything after "index.cgi")
|
||||
# ---------------------------------------------------------------------------
|
||||
URI_NO_QUERY="${REQUEST_URI%%\?*}"
|
||||
REL_PATH="/"
|
||||
case "$URI_NO_QUERY" in
|
||||
*index.cgi*)
|
||||
REL_PATH="${URI_NO_QUERY#*index.cgi}"
|
||||
;;
|
||||
esac
|
||||
REL_PATH="${REL_PATH#/}"
|
||||
REL_PATH="${REL_PATH%/}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# API requests -> python backend
|
||||
# ---------------------------------------------------------------------------
|
||||
case "$REL_PATH" in
|
||||
api/*)
|
||||
if [ -z "$PYBIN" ]; then
|
||||
json_error "500 Internal Server Error" "python3 runtime unavailable"
|
||||
fi
|
||||
if [ ! -f "${BACKEND_DIR}/api.py" ]; then
|
||||
json_error "500 Internal Server Error" "backend missing"
|
||||
fi
|
||||
export AGENTCHECKIN_BACKEND_DIR="$BACKEND_DIR"
|
||||
exec "$PYBIN" "${BACKEND_DIR}/api.py"
|
||||
;;
|
||||
esac
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Static files (with path-traversal protection)
|
||||
# ---------------------------------------------------------------------------
|
||||
if [ -z "$REL_PATH" ] || [ "$REL_PATH" = "/" ]; then
|
||||
REL_PATH="index.html"
|
||||
fi
|
||||
|
||||
case "$REL_PATH" in
|
||||
*..*) json_error "400 Bad Request" "bad path" ;;
|
||||
esac
|
||||
OLD_IFS="$IFS"
|
||||
IFS='/'
|
||||
for SEG in $REL_PATH; do
|
||||
case "$SEG" in
|
||||
''|.|*[!A-Za-z0-9._-]*) IFS="$OLD_IFS"; json_error "400 Bad Request" "bad path" ;;
|
||||
esac
|
||||
done
|
||||
IFS="$OLD_IFS"
|
||||
|
||||
TARGET_FILE="${BASE_PATH}/${REL_PATH}"
|
||||
if [ ! -f "$TARGET_FILE" ]; then
|
||||
json_error "404 Not Found" "not found"
|
||||
fi
|
||||
|
||||
case "${TARGET_FILE##*.}" in
|
||||
html|htm) mime="text/html; charset=utf-8" ;;
|
||||
css) mime="text/css; charset=utf-8" ;;
|
||||
js) mime="application/javascript; charset=utf-8" ;;
|
||||
json) mime="application/json; charset=utf-8" ;;
|
||||
png) mime="image/png" ;;
|
||||
jpg|jpeg) mime="image/jpeg" ;;
|
||||
gif) mime="image/gif" ;;
|
||||
svg) mime="image/svg+xml" ;;
|
||||
ico) mime="image/x-icon" ;;
|
||||
webmanifest) mime="application/manifest+json" ;;
|
||||
txt) mime="text/plain; charset=utf-8" ;;
|
||||
woff) mime="font/woff" ;;
|
||||
woff2) mime="font/woff2" ;;
|
||||
*) mime="application/octet-stream" ;;
|
||||
esac
|
||||
|
||||
printf 'Content-Type: %s\r\n' "$mime"
|
||||
printf 'Cache-Control: no-cache\r\n'
|
||||
printf '\r\n'
|
||||
cat "$TARGET_FILE"
|
||||
exit 0
|
||||
Reference in New Issue
Block a user