Phase 2. "Distro-agnostic" was a claim with no packaging behind it. .deb and AppImage are both built and tested here; the rpm spec and PKGBUILD are written but not yet built, since neither rpmbuild nor makepkg exists on this machine. The AppImage is deliberately not a packaged daemon. An AppImage is unprivileged by design — no install, no root, no systemd — so the execution gate cannot exist in one, and pretending otherwise would be worse than saying so. What it is instead is the try-before-you-install build: on-demand scanning, quarantine under the user's own data dir, rootkit and supply-chain checks, all with nothing left behind. Asking it for the gate prints why it cannot and how to get it. Verified with an isolated HOME: status, scan, EICAR caught, gate refusal. Two packaging bugs caught by looking rather than assuming: * mktemp -d creates 0700 and dpkg applies the staging root's mode to "/". Installing that package would have chmodded the root directory to 0700 and broken the machine. * AppRun wrote its log before creating the directory, and built a socket path that can exceed sun_path (108 bytes) when XDG_RUNTIME_DIR is long. Both fixed; the socket falls back rather than failing with an error nobody can act on. The systemd unit is hardened as far as this daemon can be. Notably ProtectSystem=strict is WRONG here and is left off on purpose: it remounts everything read-only, and quarantine has to remove a threat from wherever it landed. ProtectHome is off for the same reason. The CapabilityBoundingSet mirrors what caps.rs drops to, so the machine is protected even if the binary is replaced by one that does not reduce itself. App icon: the mark in white on a periwinkle tile, per Joe. Small sizes are not the same artwork scaled down — the mark is line-weight, so at 16px a 62% inset leaves about a pixel and a half of stroke and the head turns to mush. Each size is authored with its own inset and corner radius, and the ground goes flat below 32px because a gradient across 16 pixels is just noise. The tray ladder is untouched: those glyphs stay transparent and re-tint per state so they can sit on any panel colour. Package installs are NOT enabled by default beyond the daemon itself — exec_gate stays off until the operator turns it on, in every packaging format. 99 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
136 lines
4.5 KiB
Bash
Executable file
136 lines
4.5 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
#
|
|
# Build the Hound AppImage.
|
|
#
|
|
# What an AppImage can and cannot be, for this product:
|
|
#
|
|
# An AppImage is unprivileged by design — no install, no root, no
|
|
# systemd. The execution gate needs CAP_SYS_ADMIN and a filesystem-wide
|
|
# fanotify mark, so it is simply not available here, and pretending
|
|
# otherwise would be worse than saying so.
|
|
#
|
|
# What IS available is everything that does not need privilege:
|
|
# on-demand scanning, the quarantine vault under the user's own data
|
|
# directory, rootkit heuristics, supply-chain checks and the CLI. That
|
|
# makes this the "try it without installing anything" build, and the
|
|
# AppRun below says exactly that when the gate is asked for.
|
|
#
|
|
# Needs appimagetool on PATH (or at $APPIMAGETOOL).
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
VERSION="$(grep -m1 '^version' "$ROOT/Cargo.toml" | cut -d'"' -f2)"
|
|
OUT="${OUT:-$ROOT/dist}"
|
|
TOOL="${APPIMAGETOOL:-$(command -v appimagetool || true)}"
|
|
APPDIR="$(mktemp -d)/Hound.AppDir"
|
|
trap 'rm -rf "$(dirname "$APPDIR")"' EXIT
|
|
|
|
if [ -z "$TOOL" ]; then
|
|
echo "appimagetool not found. Set APPIMAGETOOL=/path/to/appimagetool" >&2
|
|
exit 2
|
|
fi
|
|
|
|
echo "building Hound AppImage ${VERSION}"
|
|
( cd "$ROOT" && cargo build --release -p houndd -p hound )
|
|
|
|
mkdir -p "$APPDIR"
|
|
chmod 0755 "$APPDIR"
|
|
install -Dm755 "$ROOT/target/release/hound" "$APPDIR/usr/bin/hound"
|
|
install -Dm755 "$ROOT/target/release/houndd" "$APPDIR/usr/bin/houndd"
|
|
install -Dm644 "$ROOT/crates/houndd/rules/hound-builtin.yar" \
|
|
"$APPDIR/usr/share/hound/rules/hound-builtin.yar"
|
|
|
|
# The launcher icon is the white mark on periwinkle, sized optically.
|
|
install -Dm644 "$ROOT/assets/icons/app-256.png" "$APPDIR/hound.png"
|
|
for size in 16 22 24 32 48 64 128 256 512; do
|
|
src="$ROOT/assets/icons/app-${size}.png"
|
|
[ -f "$src" ] && install -Dm644 "$src" \
|
|
"$APPDIR/usr/share/icons/hicolor/${size}x${size}/apps/hound.png"
|
|
done
|
|
install -Dm644 "$ROOT/assets/icons/hound-app.svg" \
|
|
"$APPDIR/usr/share/icons/hicolor/scalable/apps/hound.svg"
|
|
|
|
cat > "$APPDIR/hound.desktop" <<'DESKTOP'
|
|
[Desktop Entry]
|
|
Type=Application
|
|
Name=Hound Antivirus
|
|
Comment=Endpoint and supply-chain protection for Linux
|
|
Exec=hound
|
|
Icon=hound
|
|
Categories=System;Security;
|
|
Terminal=true
|
|
DESKTOP
|
|
|
|
cat > "$APPDIR/AppRun" <<'APPRUN'
|
|
#!/bin/sh
|
|
#
|
|
# Portable-mode launcher.
|
|
#
|
|
# Everything lives under the user's own directories, so the AppImage
|
|
# leaves nothing behind on the system and needs no privilege. The one
|
|
# thing it cannot do is gate execution — see below.
|
|
set -e
|
|
HERE="$(dirname "$(readlink -f "$0")")"
|
|
export PATH="$HERE/usr/bin:$PATH"
|
|
|
|
# Rules ship inside the bundle; point the daemon at them read-only.
|
|
export HOUNDD_RULES_DIR="${HOUNDD_RULES_DIR:-$HERE/usr/share/hound/rules}"
|
|
|
|
# Keep state in the user's own dirs rather than /var/lib.
|
|
export XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
|
|
export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
|
|
export HOUNDD_SOCK="${HOUNDD_SOCK:-${XDG_RUNTIME_DIR:-/tmp}/houndd.sock}"
|
|
|
|
mkdir -p "$XDG_DATA_HOME/hound" "$XDG_CONFIG_HOME/hound"
|
|
|
|
# A Unix socket path cannot exceed sun_path (108 bytes on Linux), and
|
|
# XDG_RUNTIME_DIR is not always short. Fall back rather than failing with
|
|
# an error most people cannot act on.
|
|
if [ "${#HOUNDD_SOCK}" -ge 100 ]; then
|
|
HOUNDD_SOCK="/tmp/houndd-$(id -u).sock"
|
|
export HOUNDD_SOCK
|
|
fi
|
|
|
|
# Start a private daemon if one is not already answering.
|
|
if ! "$HERE/usr/bin/hound" status >/dev/null 2>&1; then
|
|
"$HERE/usr/bin/houndd" >"${XDG_DATA_HOME}/hound/appimage.log" 2>&1 &
|
|
# Wait for the socket rather than sleeping a fixed amount.
|
|
i=0
|
|
while [ ! -S "$HOUNDD_SOCK" ] && [ $i -lt 50 ]; do
|
|
i=$((i + 1))
|
|
sleep 0.1
|
|
done
|
|
fi
|
|
|
|
case "${1:-}" in
|
|
settings)
|
|
case "${2:-} ${3:-}" in
|
|
"set exec_gate")
|
|
cat >&2 <<'MSG'
|
|
The execution gate is not available in the AppImage.
|
|
|
|
Blocking a program at execve needs CAP_SYS_ADMIN and a filesystem-wide
|
|
fanotify mark, which an unprivileged, uninstalled bundle cannot have.
|
|
Everything else works here: on-demand scanning, quarantine, rootkit
|
|
checks and supply-chain checks.
|
|
|
|
For real-time protection, install the package:
|
|
|
|
sudo apt install ./hound_*.deb
|
|
sudo hound settings set exec_gate true
|
|
MSG
|
|
exit 2
|
|
;;
|
|
esac
|
|
;;
|
|
esac
|
|
|
|
exec "$HERE/usr/bin/hound" "$@"
|
|
APPRUN
|
|
chmod 0755 "$APPDIR/AppRun"
|
|
|
|
mkdir -p "$OUT"
|
|
ARCH=x86_64 "$TOOL" --no-appstream "$APPDIR" "$OUT/Hound-${VERSION}-x86_64.AppImage" 2>&1 \
|
|
| grep -vE "^(WARNING|Warning)" || true
|
|
|
|
echo "built $OUT/Hound-${VERSION}-x86_64.AppImage"
|