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