Installed the .deb on the live server. The daemon did not start, and everything below is what that one command surfaced. 1. MemoryDenyWriteExecute=yes stopped the service dead. yara-x compiles rules to WebAssembly and JITs them, so it needs pages that go writable then executable. With W^X enforced the daemon aborts at startup: "unable to make memory executable". The unit had passed systemd-analyze verify, which checks syntax and cannot know this. Now off, with the reasoning in the unit rather than in a commit nobody will read: a hardening directive that stops the service is worse than the exposure it prevents, because the machine ends up with no antivirus at all. What compensates is spelled out beside it. 2. Hound detected itself. The goodware gate reported /usr/bin/houndd as Linux.Coinminer.XMRig and Linux.Rootkit.Preload. Correctly: the built-in pack matches on "stratum+tcp://", "xmrig", "RTLD_NEXT" and "ld.so.preload", and the pack was embedded verbatim, so the daemon's own binary contained all of them. Not cosmetic. With the execution gate armed, Hound would have refused to execute itself or quarantined its own binary — a scanner that eats its own daemon the moment protection is switched on. The pack is now XOR-masked at build time (build.rs) and unmasked at startup. Not a secret — the rules are open source — the only job is keeping the literal bytes out of the executable. Two regression tests: the embedded blob carries no plaintext rule strings, and a built daemon binary in target/ carries none either. 3. The postinst copied the built-in pack into /var/lib/hound/rules, where the daemon compiled it a second time and logged a duplicate declaration on every start. That directory is for ADDITIONAL packs; the built-ins live in the binary. Removed from deb, rpm and AUR. 4. The daemon and the CLI disagreed about the socket. systemd gives the service /run/hound; the CLI looked in $XDG_RUNTIME_DIR and reported the daemon unreachable — technically true, entirely unhelpful. default_socket_path() now prefers /run/hound when it exists, the unit states HOUNDD_SOCK explicitly, and a permission error on the socket says "try: sudo hound" instead of "Permission denied". Also: Recommends: clamav-daemon was wrong and apt duly installed clamd, which took 970 MB of RSS on the live server. clamd is an optional arm's-length engine, so it is a Suggests. I stopped and disabled the copy my install pulled in. Verified on the server after fixing: service active, status reports the engine and the gate, EICAR caught, rootkit scan clean, and the goodware gate passes across 3,955 system binaries including the now-installed houndd. The gate remains OFF. Turning it on for the host that serves Caddy is a separate decision. 295 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
185 lines
6 KiB
Bash
Executable file
185 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)
|
|
Suggests: 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
|
|
|
|
# The built-in rules are compiled INTO the binary; /var/lib/hound/rules
|
|
# is for additional packs only. Copying the built-ins there made the
|
|
# daemon compile them twice and log a duplicate-declaration error on
|
|
# every start. The copy under /usr/share is documentation, not input.
|
|
|
|
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/^/ /'
|