RedFlag/installer/linux/inspect-deb.sh
Fimeg 765ec4188f publish: carry the Windows product into the projection
The tree adds the installer, service and test paths admitted by this source commit. Nothing else changes about what may cross.

Source-Sha: 3e4a3aa4ce092e9e3f51bc78daf3ddc14d9638f8

Policy-Sha: 3e4a3aa4ce092e9e3f51bc78daf3ddc14d9638f8

Tree-Digest: 89d6fce3828a0e0de0c8eeb41ee6750f4462217e2a04b5190297134d7f4a50c3
2026-09-09 12:47:34 -04:00

151 lines
6.3 KiB
Shell

#!/bin/bash
# inspect-deb.sh — validate a built RedFlag .deb without installing it.
# Suitable for CI: exits non-zero on any failed assertion and prints a
# component|result|detail style report.
#
# Usage: installer/linux/inspect-deb.sh dist/redflag_0.3.0_amd64.deb [--version 0.3.0]
#
# Checks: control metadata, declared runtime dependencies, required payload
# paths and modes, maintainer-script shell syntax, and that the packaged
# binaries are Linux x86-64 ELF executables. It cannot verify installed-system
# behaviour — that needs a real install on a Debian host.
set -uo pipefail
DEB="${1:-}"
[ -n "$DEB" ] || { echo "usage: $0 <package.deb> [--version X.Y.Z]" >&2; exit 2; }
shift || true
WANT_VERSION=""
while [ $# -gt 0 ]; do
case "$1" in
--version) WANT_VERSION="$2"; shift 2 ;;
*) echo "unknown option: $1" >&2; exit 2 ;;
esac
done
[ -f "$DEB" ] || { echo "no such package: $DEB" >&2; exit 2; }
command -v dpkg-deb >/dev/null 2>&1 || { echo "dpkg-deb not found" >&2; exit 2; }
FAILURES=0
ok() { echo " [ok] $1: $2"; }
bad() { echo " [FAIL] $1: $2"; FAILURES=$((FAILURES + 1)); }
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
dpkg-deb --control "$DEB" "$TMP/DEBIAN" >/dev/null 2>&1 || { echo "cannot read control archive" >&2; exit 2; }
dpkg-deb --contents "$DEB" > "$TMP/contents" 2>/dev/null || { echo "cannot list contents" >&2; exit 2; }
dpkg-deb --extract "$DEB" "$TMP/root" >/dev/null 2>&1 || { echo "cannot extract payload" >&2; exit 2; }
echo "=== RedFlag package inspection: $DEB ==="
# ---- control ----------------------------------------------------------------
CONTROL="$TMP/DEBIAN/control"
field() { grep -m1 -i "^$1:" "$CONTROL" | sed "s/^[^:]*:[[:space:]]*//"; }
[ "$(field Package)" = redflag ] && ok control "Package: redflag" || bad control "unexpected Package: $(field Package)"
[ "$(field Architecture)" = amd64 ] && ok control "Architecture: amd64" || bad control "unexpected Architecture: $(field Architecture)"
PKG_VERSION="$(field Version)"
if [ -z "$PKG_VERSION" ] || echo "$PKG_VERSION" | grep -q '@'; then
bad control "Version not substituted: '$PKG_VERSION'"
elif [ -n "$WANT_VERSION" ] && [ "$PKG_VERSION" != "$WANT_VERSION" ]; then
bad control "Version $PKG_VERSION != expected $WANT_VERSION"
else
ok control "Version: $PKG_VERSION"
fi
grep -qi '^Maintainer:' "$CONTROL" && ok control "Maintainer present" || bad control "Maintainer missing"
grep -qi '^Description:' "$CONTROL" && ok control "Description present" || bad control "Description missing"
# ---- dependencies -----------------------------------------------------------
DEPENDS="$(tr '\n' ' ' < "$CONTROL" | sed -n 's/.*Depends:\(.*\)/\1/p' | sed 's/[A-Z][a-zA-Z-]*:.*//')"
for dep in libc6 sudo systemd python3 python3-cryptography libqt6quick6 libqt6qml6 qml6-module-qtquick; do
case "$DEPENDS" in
*"$dep"*) ok depends "$dep declared" ;;
*) bad depends "$dep not declared" ;;
esac
done
case "$DEPENDS" in
*polkit*) ok depends "polkit declared" ;;
*) bad depends "polkit not declared (transient-unit rule would be inert)" ;;
esac
# ---- payload ----------------------------------------------------------------
check_file() {
local path="$1" mode="$2"
if [ ! -e "$TMP/root$path" ]; then
bad payload "$path missing"
return
fi
local actual; actual="$(stat -c%a "$TMP/root$path")"
if [ -n "$mode" ] && [ "$actual" != "$mode" ]; then
bad payload "$path mode $actual, expected $mode"
else
ok payload "$path ($actual)"
fi
}
check_file /usr/bin/redflag-agent 755
check_file /usr/bin/redflag-helper 755
check_file /usr/bin/redflag-desktop 755
check_file /lib/systemd/system/redflag-agent.service 644
check_file /etc/sudoers.d/redflag-agent 440
check_file /etc/polkit-1/rules.d/50-redflag-agent.rules 644
check_file /etc/xdg/autostart/redflag-desktop.desktop 644
check_file /usr/share/applications/redflag-desktop.desktop 644
check_file /usr/lib/redflag/provision-standalone-authority.sh 755
check_file /usr/share/doc/redflag/copyright 644
check_file /usr/share/doc/redflag/THIRD_PARTY_LICENSES.md 644
# Debian policy: nothing under /usr/local may be shipped in the archive.
if grep -qE ' \./usr/local/' "$TMP/contents"; then
bad policy "package ships files under /usr/local (postinst must create those symlinks instead)"
else
ok policy "no /usr/local content in the archive"
fi
for b in redflag-agent redflag-helper redflag-desktop; do
p="$TMP/root/usr/bin/$b"
[ -f "$p" ] || continue
magic="$(head -c 20 "$p" | od -An -tx1 | tr -d ' \n')"
if [ "${magic:0:8}" = "7f454c46" ] && [ "${magic:36:4}" = "3e00" ]; then
ok binary "$b is x86-64 ELF ($(stat -c%s "$p") bytes)"
else
bad binary "$b is not a Linux x86-64 ELF"
fi
done
# ---- maintainer scripts -----------------------------------------------------
for s in preinst postinst prerm postrm; do
f="$TMP/DEBIAN/$s"
if [ ! -f "$f" ]; then bad maintscript "$s missing"; continue; fi
[ -x "$f" ] || bad maintscript "$s not executable"
if sh -n "$f" 2>/dev/null; then ok maintscript "$s syntax ok"; else bad maintscript "$s syntax error"; fi
grep -q '@VERSION@' "$f" && bad maintscript "$s has unsubstituted @VERSION@"
done
# The provisioning script is the only authority logic: the package must call it,
# not reimplement it. A postinst that mints keys itself is a review failure.
if grep -q 'provision-standalone-authority.sh' "$TMP/DEBIAN/postinst"; then
ok authority "postinst delegates to the provisioning script"
else
bad authority "postinst does not call provision-standalone-authority.sh"
fi
if grep -qE 'mint --init-key|authority_local\.key' "$TMP/DEBIAN/postinst"; then
bad authority "postinst touches authority key material directly"
else
ok authority "postinst does not duplicate key handling"
fi
if grep -q 'refresh_token' "$TMP/DEBIAN/postinst"; then
ok authority "postinst checks for fleet enrollment material"
else
bad authority "postinst has no fleet-install guard"
fi
if [ -f "$TMP/DEBIAN/md5sums" ]; then ok integrity "md5sums present"; else bad integrity "md5sums missing"; fi
if [ -f "$TMP/DEBIAN/conffiles" ]; then ok integrity "conffiles present"; else bad integrity "conffiles missing"; fi
echo ""
if [ "$FAILURES" -gt 0 ]; then
echo "Result: $FAILURES check(s) failed"
exit 1
fi
echo "Result: all checks passed"