Files
live_recorder/scripts/verify-fnos-package.sh

121 lines
4.6 KiB
Bash
Executable File

#!/bin/bash
set -euo pipefail
PACKAGE=${1:?usage: verify-fnos-package.sh package.fpk [expected-version]}
EXPECTED_VERSION=${2:-}
VERIFY_TMP_ROOT="${LIVERECORDER_VERIFY_TMPDIR:-${TMPDIR:-/tmp}}"
MAX_APP_UNCOMPRESSED_BYTES=$((512 * 1024 * 1024))
mkdir -p "$VERIFY_TMP_ROOT"
WORK_DIR=$(mktemp -d "${VERIFY_TMP_ROOT%/}/fnos-package-verify.XXXXXX")
trap 'rm -rf -- "$WORK_DIR"' EXIT
manifest_value() {
sed -n "s/^$1[[:space:]]*=[[:space:]]*//p" "$WORK_DIR/manifest" | head -n 1 | tr -d '\r'
}
tar -xzf "$PACKAGE" -C "$WORK_DIR"
appname=$(manifest_value appname)
version=$(manifest_value version)
test "$appname" = "liverecorder" || { printf 'unexpected fnOS appname: %s\n' "$appname" >&2; exit 1; }
test -n "$version"
if [ -n "$EXPECTED_VERSION" ] && [ "$version" != "$EXPECTED_VERSION" ]; then
printf 'unexpected fnOS version: expected %s, got %s\n' "$EXPECTED_VERSION" "$version" >&2
exit 1
fi
test "$(manifest_value platform)" = "x86"
test -x "$WORK_DIR/cmd/main"
test -x "$WORK_DIR/cmd/install_callback"
test -s "$WORK_DIR/wizard/install"
test -s "$WORK_DIR/wizard/upgrade"
test -s "$WORK_DIR/ICON.PNG"
test -s "$WORK_DIR/ICON_256.PNG"
expected=$(manifest_value checksum)
actual=$(md5sum "$WORK_DIR/app.tgz" | cut -d' ' -f1)
test -n "$expected"
test "$expected" = "$actual"
gzip -t "$WORK_DIR/app.tgz"
gzip -dc "$WORK_DIR/app.tgz" >"$WORK_DIR/app.tar"
uncompressed_bytes=$(wc -c <"$WORK_DIR/app.tar")
if [ "$uncompressed_bytes" -gt "$MAX_APP_UNCOMPRESSED_BYTES" ]; then
printf 'app.tgz expands to %s bytes; limit is %s bytes\n' \
"$uncompressed_bytes" "$MAX_APP_UNCOMPRESSED_BYTES" >&2
exit 1
fi
tar -tf "$WORK_DIR/app.tar" >"$WORK_DIR/app-files.txt"
if awk '/^\// || /(^|\/)\.\.($|\/)/ { unsafe = 1; exit } END { exit unsafe ? 0 : 1 }' "$WORK_DIR/app-files.txt"; then
printf 'app.tgz contains an unsafe member path\n' >&2
exit 1
fi
tar -tvf "$WORK_DIR/app.tar" >"$WORK_DIR/app-metadata.txt"
if awk '
/^l/ {
marker = " -> "
offset = index($0, marker)
if (offset > 0) {
target = substr($0, offset + length(marker))
if (target ~ /^\//) { unsafe = 1; exit }
}
}
/^h/ {
marker = " link to "
offset = index($0, marker)
if (offset > 0) {
target = substr($0, offset + length(marker))
if (target ~ /^\//) { unsafe = 1; exit }
}
}
END { exit unsafe ? 0 : 1 }
' "$WORK_DIR/app-metadata.txt"; then
printf 'app.tgz contains an unsafe symbolic or hard link\n' >&2
exit 1
fi
grep -q '^server/wwwroot/index.html$' "$WORK_DIR/app-files.txt"
grep -q '^ui/config$' "$WORK_DIR/app-files.txt"
grep -q '^ui/images/icon_64.png$' "$WORK_DIR/app-files.txt"
grep -q '^server/LiveRecorder.WebApi$' "$WORK_DIR/app-files.txt"
grep -q '^server/Platforms/Douyin/Signing/sign-xbogus.js$' "$WORK_DIR/app-files.txt"
grep -q '^runtime/bin/curl$' "$WORK_DIR/app-files.txt"
grep -q '^runtime/etc/ssl/certs/ca-certificates.crt$' "$WORK_DIR/app-files.txt"
test "$(manifest_value install_dep_apps)" = "nxsir.postgresql:nodejs_v22"
if grep -Eq '^runtime/(bin/node|usr/(lib|share)/postgresql/|lib/(libLLVM|libz3))' "$WORK_DIR/app-files.txt"; then
printf 'Live Recorder must use shared PostgreSQL and the fnOS nodejs_v22 dependency\n' >&2
exit 1
fi
if grep -Eq '^runtime/.*/(ffmpeg|ffprobe)$' "$WORK_DIR/app-files.txt"; then
printf 'ffmpeg and ffprobe must come from the fnOS system environment\n' >&2
exit 1
fi
if grep -Eq '^runtime/lib/(ld-linux-.*|libc\.so\..*|libBrokenLocale\.so\..*|libanl\.so\..*|libdl\.so\..*|libm(vec)?\.so\..*|libnss_(compat|dns|files|hesiod)\.so\..*|libpthread\.so\..*|libresolv\.so\..*|librt\.so\..*|libthread_db\.so\..*|libutil\.so\..*)$' "$WORK_DIR/app-files.txt"; then
printf 'the FPK must use the fnOS glibc and matching system loader\n' >&2
exit 1
fi
if grep -Eq '^(data|records|postgres|log|var)/' "$WORK_DIR/app-files.txt"; then
printf 'persistent data must not be included in app.tgz\n' >&2
exit 1
fi
mkdir -p "$WORK_DIR/app"
tar -xf "$WORK_DIR/app.tar" -C "$WORK_DIR/app"
while IFS= read -r -d '' link_path; do
target=$(readlink -- "$link_path")
case "$target" in
/*) printf 'absolute symbolic link in app.tgz: %s -> %s\n' "$link_path" "$target" >&2; exit 1 ;;
esac
resolved=$(realpath -m -- "$(dirname -- "$link_path")/$target")
case "$resolved" in
"$WORK_DIR/app"/*) ;;
*) printf 'escaping symbolic link in app.tgz: %s -> %s\n' "$link_path" "$target" >&2; exit 1 ;;
esac
done < <(find "$WORK_DIR/app" -type l -print0)
printf 'fnOS package verified: %s (%s bytes uncompressed)\n' "$PACKAGE" "$uncompressed_bytes"