Files
fnos-agent-checkin/AgentCheckin/app/ui/index.cgi
T
cbb a3b84d5ce2 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 前端回归 + 真实时钟调度测试
2026-10-13 19:09:48 +08:00

131 lines
4.3 KiB
Bash

#!/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