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>
186 lines
6 KiB
Bash
Executable file
186 lines
6 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
#
|
|
# Build a .deb for Ubuntu / Debian / Linux Mint.
|
|
#
|
|
# Deliberately hand-rolled rather than cargo-deb: the package needs a
|
|
# postinst that creates the vault with the right mode, a conffile that
|
|
# survives upgrades, and a unit that is enabled but whose gate stays off
|
|
# until the operator turns it on. That is easier to read as a script than
|
|
# as a pile of metadata, and it is the thing most likely to need auditing.
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
VERSION="$(grep -m1 '^version' "$ROOT/Cargo.toml" | cut -d'"' -f2)"
|
|
ARCH="$(dpkg --print-architecture)"
|
|
OUT="${OUT:-$ROOT/dist}"
|
|
STAGE="$(mktemp -d)"
|
|
trap 'rm -rf "$STAGE"' EXIT
|
|
# mktemp -d creates 0700, and dpkg applies the staging root's mode to "/".
|
|
# Installing this package would chmod / to 0700 and break the machine.
|
|
chmod 0755 "$STAGE"
|
|
|
|
echo "building hound ${VERSION} (${ARCH})"
|
|
( cd "$ROOT" && cargo build --release -p houndd -p hound )
|
|
|
|
install -Dm755 "$ROOT/target/release/houndd" "$STAGE/usr/bin/houndd"
|
|
install -Dm755 "$ROOT/target/release/hound" "$STAGE/usr/bin/hound"
|
|
install -Dm644 "$ROOT/packaging/systemd/houndd.service" \
|
|
"$STAGE/lib/systemd/system/houndd.service"
|
|
install -Dm644 "$ROOT/crates/houndd/rules/hound-builtin.yar" \
|
|
"$STAGE/usr/share/hound/rules/hound-builtin.yar"
|
|
install -Dm644 "$ROOT/README.md" "$STAGE/usr/share/doc/hound/README.md"
|
|
|
|
# Launcher icon: the white mark on a periwinkle tile (app-*.png), not the
|
|
# bare brand mark. The tray ladder is a different family and ships with
|
|
# the GUI, because tray glyphs must stay transparent to sit on any panel.
|
|
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" \
|
|
"$STAGE/usr/share/icons/hicolor/${size}x${size}/apps/hound.png"
|
|
done
|
|
install -Dm644 "$ROOT/assets/icons/hound-app.svg" \
|
|
"$STAGE/usr/share/icons/hicolor/scalable/apps/hound.svg"
|
|
|
|
install -Dm644 /dev/stdin "$STAGE/usr/share/applications/hound.desktop" <<'DESKTOP'
|
|
[Desktop Entry]
|
|
Type=Application
|
|
Name=Hound Antivirus
|
|
GenericName=Antivirus
|
|
Comment=Endpoint and supply-chain protection for Linux
|
|
Exec=hound
|
|
Icon=hound
|
|
Categories=System;Security;Utility;
|
|
Keywords=antivirus;malware;security;scan;supply chain;
|
|
Terminal=true
|
|
DESKTOP
|
|
|
|
mkdir -p "$STAGE/DEBIAN"
|
|
|
|
cat > "$STAGE/DEBIAN/control" <<CONTROL
|
|
Package: hound
|
|
Version: ${VERSION}
|
|
Section: utils
|
|
Priority: optional
|
|
Architecture: ${ARCH}
|
|
Maintainer: Hound <support@houndav.com>
|
|
Depends: libc6 (>= 2.34)
|
|
Recommends: clamav-daemon
|
|
Homepage: https://houndav.com
|
|
Description: Hound Antivirus for Linux
|
|
Endpoint and supply-chain protection built for the distributions people
|
|
actually run. Scanning is yara-x in process; real-time protection uses
|
|
fanotify, so a binary can be refused at execve rather than reported
|
|
after it has already run.
|
|
.
|
|
The execution gate is installed switched OFF. It needs CAP_SYS_ADMIN and
|
|
covers the whole root filesystem, so turning it on is the operator's
|
|
decision: hound settings set exec_gate true
|
|
CONTROL
|
|
|
|
cat > "$STAGE/DEBIAN/conffiles" <<'CONFFILES'
|
|
/etc/hound/hound.toml
|
|
CONFFILES
|
|
|
|
install -Dm644 /dev/stdin "$STAGE/etc/hound/hound.toml" <<'CONF'
|
|
# Hound Antivirus configuration.
|
|
#
|
|
# Live settings are managed through `hound settings` and stored per user;
|
|
# this file holds the machine-wide defaults the daemon starts from.
|
|
|
|
# Deny execution until a verdict is returned. Needs CAP_SYS_ADMIN.
|
|
# Off by default: it covers the whole root filesystem, and that is the
|
|
# operator's call to make rather than the installer's.
|
|
exec_gate = false
|
|
|
|
# Mounts the gate covers. Empty means the root filesystem.
|
|
exec_gate_paths = []
|
|
|
|
# Never held for a verdict.
|
|
exclude_paths = ["/proc", "/sys", "/dev", "/run", "/var/lib/docker"]
|
|
|
|
# Files larger than this are allowed through unread.
|
|
max_file_size_mb = 100
|
|
|
|
# "quarantine" or "alert".
|
|
on_detect = "quarantine"
|
|
CONF
|
|
|
|
cat > "$STAGE/DEBIAN/postinst" <<'POSTINST'
|
|
#!/bin/sh
|
|
set -e
|
|
|
|
case "$1" in
|
|
configure)
|
|
# The vault holds live malware: root-only, and on a filesystem where
|
|
# nothing in it can be executed even by accident.
|
|
mkdir -p /var/lib/hound/vault /var/lib/hound/rules /var/log/hound
|
|
chmod 0700 /var/lib/hound/vault
|
|
chmod 0755 /var/lib/hound /var/lib/hound/rules
|
|
chmod 0750 /var/log/hound
|
|
|
|
# Seed the built-in rules where the daemon looks for packs, so an
|
|
# offline install still detects something.
|
|
if [ -f /usr/share/hound/rules/hound-builtin.yar ]; then
|
|
cp -n /usr/share/hound/rules/hound-builtin.yar /var/lib/hound/rules/ || true
|
|
fi
|
|
|
|
if [ -d /run/systemd/system ]; then
|
|
systemctl daemon-reload || true
|
|
systemctl enable houndd.service || true
|
|
systemctl restart houndd.service || true
|
|
fi
|
|
|
|
echo ""
|
|
echo "Hound is installed and scanning on demand."
|
|
echo ""
|
|
echo " hound status what the daemon sees"
|
|
echo " hound scan ~/Downloads scan a directory"
|
|
echo ""
|
|
echo "Real-time execution blocking is OFF until you turn it on:"
|
|
echo ""
|
|
echo " sudo hound settings set exec_gate true"
|
|
echo ""
|
|
;;
|
|
esac
|
|
exit 0
|
|
POSTINST
|
|
|
|
cat > "$STAGE/DEBIAN/prerm" <<'PRERM'
|
|
#!/bin/sh
|
|
set -e
|
|
case "$1" in
|
|
remove|deconfigure)
|
|
if [ -d /run/systemd/system ]; then
|
|
systemctl stop houndd.service || true
|
|
systemctl disable houndd.service || true
|
|
fi
|
|
;;
|
|
esac
|
|
exit 0
|
|
PRERM
|
|
|
|
cat > "$STAGE/DEBIAN/postrm" <<'POSTRM'
|
|
#!/bin/sh
|
|
set -e
|
|
case "$1" in
|
|
purge)
|
|
# The vault is deliberately NOT removed on `remove`, only on `purge`,
|
|
# and even then only after saying so: it may be the sole copy of
|
|
# evidence somebody still needs.
|
|
echo "Removing the Hound quarantine vault at /var/lib/hound/vault"
|
|
rm -rf /var/lib/hound /var/log/hound
|
|
;;
|
|
esac
|
|
if [ -d /run/systemd/system ]; then
|
|
systemctl daemon-reload || true
|
|
fi
|
|
exit 0
|
|
POSTRM
|
|
|
|
chmod 0755 "$STAGE/DEBIAN/postinst" "$STAGE/DEBIAN/prerm" "$STAGE/DEBIAN/postrm"
|
|
|
|
mkdir -p "$OUT"
|
|
DEB="$OUT/hound_${VERSION}_${ARCH}.deb"
|
|
fakeroot dpkg-deb --build --root-owner-group "$STAGE" "$DEB" >/dev/null
|
|
echo "built $DEB"
|
|
dpkg-deb -I "$DEB" | sed 's/^/ /'
|