- 原生 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 前端回归 + 真实时钟调度测试
131 lines
4.3 KiB
Bash
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
|