- P0 path-traversal fix: run-name class is dot-free with \z anchors, so '.', '..', and %0A-trailing values can never escape TRIMSP_TMPROOT. The deployed path allowlist is derived from the configured root instead of the old hardcoded /tmp prefix. - Replacing external with an alarm() watchdog that kills the engine's whole process group (setpgrp + kill KILL -pid), matching muSRFlow; describe_result maps -2/-1/signal to 124/127/128+sig. - prune_stale_jobs() ages out dirs (TrimSP.cfg marker only), orphaned .tgz, and leaked per-request logs; safe in shared /tmp (symlinks skipped, remove_tree safe mode, never touches non-trimsp dirs). - Archive is serialized per run with flock (.trimsp-lock); per-point engine runs stay concurrent. - Symlink/non-dir run dirs refused; mode 0700 so results are not world-readable. read/download also reject symlinked targets. - Request caps: MAX_REQUEST_LINES 20000, MAX_REQUEST_BYTES 4 MiB -> 413. - action=health deployment self-check; env overrides TRIMSP_BIN, TMPROOT, CMD_TIMEOUT, JOB_MAXAGE (sed anchor for the binary path kept intact). - Wire contract preserved: ok/phase/message/exitCode/archiveUrl/log JSON, text read, gzip download, real HTTP statuses. No TrimSPweb.js change. - New tests/cgi-tests.sh drives the real CGI offline (18 cases: traversal, %0A, escaping round-trip, success/fail/hang engine, 124 watchdog, archive+- download, pruner, symlink refusal, 413). deploy-web.sh now verifies the sed substitution embedded the selected engine path.
152 lines
6.4 KiB
Bash
Executable File
152 lines
6.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Copyright (C) 2026 Zaher Salman
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
set -euo pipefail
|
|
|
|
project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
web_target="${TRIMSP_WEB_TARGET:-/var/www/html/TRIMSP-NL}"
|
|
cgi_target="${TRIMSP_CGI_TARGET:-/var/www/cgi-bin/singleTrimSP.cgi}"
|
|
trimsp_binary="${TRIMSP_BIN_PATH:-/usr/local/bin/trimspNL}"
|
|
plotly_source="$project_root/node_modules/plotly.js-dist/plotly.js"
|
|
plotly_cdn="${TRIMSP_PLOTLY_CDN:-https://cdn.jsdelivr.net/npm/plotly.js-dist@1.58.5/plotly.js}"
|
|
|
|
# Staging files created by this script, removed on exit so a CDN download does
|
|
# not leak into /tmp after an early failure.
|
|
tmp_files=""
|
|
trap 'rm -f $tmp_files' EXIT
|
|
|
|
if [[ ! -x "$trimsp_binary" ]]; then
|
|
printf 'Installed TRIM.SP-NL binary is not executable: %s\n' "$trimsp_binary" >&2
|
|
printf 'Install it first or set TRIMSP_BIN_PATH.\n' >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -f "$plotly_source" ]]; then
|
|
printf 'Plotly browser bundle not found locally: %s\n' "$plotly_source" >&2
|
|
printf 'Downloading bundle from CDN instead ...\n' >&2
|
|
if ! command -v curl >/dev/null 2>&1; then
|
|
printf 'curl is required to download the Plotly bundle.\n' >&2
|
|
printf 'Install curl, run npm install, or set TRIMSP_PLOTLY_CDN.\n' >&2
|
|
exit 1
|
|
fi
|
|
plotly_tmp="$(mktemp)"
|
|
tmp_files="$tmp_files $plotly_tmp"
|
|
if ! curl -fsSL "$plotly_cdn" -o "$plotly_tmp"; then
|
|
printf 'Failed to download Plotly bundle from %s\n' "$plotly_cdn" >&2
|
|
printf 'Run npm install before deploying.\n' >&2
|
|
exit 1
|
|
fi
|
|
printf 'Downloaded Plotly bundle from %s\n' "$plotly_cdn"
|
|
plotly_source="$plotly_tmp"
|
|
fi
|
|
|
|
perl -c "$project_root/singleTrimSP.cgi" >/dev/null
|
|
|
|
# Install only files used by the browser application. Electron-specific code,
|
|
# package metadata, tests, and Fortran sources stay outside the document root.
|
|
install -d -m 0755 "$web_target" "$(dirname "$cgi_target")"
|
|
install -m 0644 \
|
|
"$project_root/TrimSP.html" \
|
|
"$project_root/quick-start.html" \
|
|
"$project_root/TrimSPlib.js" \
|
|
"$project_root/TrimSPweb.js" \
|
|
"$project_root/myplots.js" \
|
|
"$project_root/ZGUI.css" \
|
|
"$project_root/plotly.css" \
|
|
"$project_root/icon.png" \
|
|
"$web_target/"
|
|
install -m 0644 "$plotly_source" "$web_target/plotly.js"
|
|
ln -sfn TrimSP.html "$web_target/index.html"
|
|
|
|
# Brand the installed help page with the current release version, matching the
|
|
# About dialog in the Electron edition. The source keeps a __TRIMSP_VERSION__
|
|
# token that renders as "development build" when left unsubstituted; the
|
|
# installed copy is rewritten with the version from package.json. Idempotent:
|
|
# an already-branded file contains no token, so re-running matches nothing.
|
|
if command -v node >/dev/null 2>&1; then
|
|
version="$(node -p 'require(process.argv[1]).version' "$project_root/package.json")"
|
|
tmp_qs="$(mktemp)"
|
|
tmp_files="$tmp_files $tmp_qs"
|
|
sed "s#__TRIMSP_VERSION__#$version#g" "$web_target/quick-start.html" > "$tmp_qs"
|
|
install -m 0644 "$tmp_qs" "$web_target/quick-start.html"
|
|
printf 'Branded %s with TRIMSP-NL version %s\n' "$web_target/quick-start.html" "$version"
|
|
else
|
|
printf 'node not found; quick-start.html keeps the source version token\n'
|
|
fi
|
|
|
|
# Keep the repository CGI generic while embedding the selected installed
|
|
# binary path in the deployed copy.
|
|
cgi_stage="$(mktemp)"
|
|
tmp_files="$tmp_files $cgi_stage"
|
|
escaped_trimsp_binary="${trimsp_binary//\\/\\\\}"
|
|
escaped_trimsp_binary="${escaped_trimsp_binary//&/\\&}"
|
|
escaped_trimsp_binary="${escaped_trimsp_binary//|/\\|}"
|
|
escaped_trimsp_binary="${escaped_trimsp_binary//\"/\\\"}"
|
|
sed 's|^my \$trimsp_bin = .*|my $trimsp_bin = "'"$escaped_trimsp_binary"'";|' \
|
|
"$project_root/singleTrimSP.cgi" > "$cgi_stage"
|
|
perl -c "$cgi_stage" >/dev/null
|
|
install -m 0755 "$cgi_stage" "$cgi_target"
|
|
|
|
# The installed CGI must embed the selected engine path exactly; a future
|
|
# source layout change could silently break the sed substitution above.
|
|
if ! grep -Fq 'my $trimsp_bin = "'"$trimsp_binary"'";' "$cgi_target"; then
|
|
printf 'Deployed CGI does not embed the selected engine path %s\n' "$trimsp_binary" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# The browser assets and the CGI must be readable (and, for the CGI,
|
|
# executable) by the web server. On the live server the web space is owned by
|
|
# the apache user; on a developer machine the files may instead be owned by
|
|
# the deploying user and served straight from a user-owned tree. Determine the
|
|
# owner to hand the files to, preferring an explicit override, then a real
|
|
# web-server account, and keeping the current owner when it is already the
|
|
# deploying user (the common developer-machine case, where the deploying user
|
|
# simply owns the web space and chowning to a nonexistent apache user would
|
|
# fail).
|
|
web_owner="${TRIMSP_WEB_OWNER:-}"
|
|
web_group="${TRIMSP_WEB_GROUP:-$web_owner}"
|
|
|
|
if [ -z "$web_owner" ]; then
|
|
# Detect from the target filesystem: if the deployed tree is already owned
|
|
# by the invoking user, leave ownership untouched.
|
|
if [ "$(stat -c %u "$web_target")" = "$(id -u)" ]; then
|
|
web_owner=""
|
|
web_group=""
|
|
else
|
|
# Prefer a real web-server account when one exists.
|
|
for cand in apache www-data httpd; do
|
|
if id "$cand" >/dev/null 2>&1; then
|
|
web_owner="$cand"
|
|
web_group="$cand"
|
|
break
|
|
fi
|
|
done
|
|
# With no matching account, fall back to the invoking user (the trees
|
|
# were written by this deploy, so they are already owned by them).
|
|
[ -z "$web_owner" ] && web_owner="$(id -un)" && web_group="$(id -gn)"
|
|
fi
|
|
fi
|
|
|
|
if [ -n "$web_owner" ]; then
|
|
chown -R "$web_owner:$web_group" "$web_target/"
|
|
chown "$web_owner:$web_group" "$cgi_target"
|
|
chmod -R u+rX "$web_target/" 2>/dev/null || true
|
|
fi
|
|
|
|
if command -v restorecon >/dev/null 2>&1; then
|
|
restorecon -RF "$web_target" "$cgi_target" || true
|
|
fi
|
|
|
|
printf 'Installed TRIMSP-NL web assets in %s\n' "$web_target"
|
|
printf 'Installed TRIMSP-NL CGI runner at %s\n' "$cgi_target"
|
|
printf 'Using installed TRIM.SP-NL engine at %s\n' "$trimsp_binary"
|
|
if [ -n "$web_owner" ]; then
|
|
printf 'Ownership set to %s:%s\n' "$web_owner" "$web_group"
|
|
else
|
|
printf 'Ownership left unchanged (deployed by the owning user)\n'
|
|
fi
|
|
printf '\nThe web server must provide:\n'
|
|
printf ' /cgi-bin/singleTrimSP.cgi -> %s\n' "$cgi_target"
|
|
printf 'For SELinux deployments, verify CGI execution and write access with:\n'
|
|
printf ' ausearch -m avc -ts recent\n' |