Three tray bugs today, each caused by fixing the previous one without looking at what else the fix touched. Worth writing down as one thing rather than three. **Two dogs, again.** 0.1.4 taught the app to notice its own package being replaced and reopen. 0.1.5 added the same behaviour to `hound update`, which is the better mechanism because it works no matter which version was running — and I did not remove the first. Both fire during an update: the running app re-execs under a new pid while apt is working, so the updater kills a pid that has already moved and starts a second instance beside it. The in-app restart now waits eight seconds before acting. If the updater is doing its job this process is terminated during the pause and never restarts itself; what survives the wait is the only case the updater cannot cover, which is somebody running `apt upgrade` directly. Two mechanisms, one arbiter. **The launcher stopped working.** The single-instance lock added in 0.1.5 to prevent the duplicate icon made a second launch exit quietly, so clicking the desktop shortcut while Hound was open did nothing at all — no window, no error, no feedback. It now hands the request to the running instance, which shows, unminimises and focuses. The same channel the right-click scan already used: a launch with paths means "scan these", a launch without means "show yourself". Also in this release, and the reason the version is what it says it is: the workspace version did not get bumped. Adding the hound-watch crate pushed `version =` from line 6 to line 7 of Cargo.toml, and the bump was a line-numbered sed. The three GUI files went to 0.1.10 and the workspace stayed at 0.1.9, so the package built as 0.1.9 while the manifest would have advertised 0.1.10 — every installed agent would have offered an update forever, installed it, and still seen one available. `the_declared_versions_all_agree` caught it before the build finished. tools/bump-version.sh now does this by matching content and section rather than line numbers, asserts every substitution actually happened, and refuses to write invalid JSON. Adds crates/hound-watch: the registry firehose. npm publishes a CouchDB _changes stream and PyPI an RSS feed of every new project, both public, so this needs no user data at all — which is the answer to how a product with no telemetry grows its detection. Each new package is handed to the detectors Hound already ships, so there is one definition of "this install script is hostile" and a rule that fires in CI fires identically on the firehose. Verified against the live registries: 240 releases in one poll, 60 triaged, parsers tested against captured responses rather than the network. 476 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|---|---|---|
| action | ||
| assets/icons | ||
| crates | ||
| dist | ||
| gui | ||
| packaging | ||
| site | ||
| tools | ||
| .editorconfig | ||
| .env.example | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
| rust-toolchain.toml | ||
Hound Antivirus
A premium, freemium antivirus for Linux. One engine, three faces:
a Rust daemon (houndd), a CLI (hound), and a Tauri GUI with a
system-tray sentinel that changes color with your security state.
Built for the distros people actually run: Ubuntu, Debian, Linux Mint, and anything else that ships ClamAV.
Repository layout
antivirus/
├── Cargo.toml # Rust workspace
├── crates/
│ ├── hound-api/ # shared wire types + socket client (daemon/CLI/GUI all use it)
│ ├── houndd/ # the daemon: Unix-socket API over a pluggable engine
│ └── hound/ # CLI client
├── assets/icons/ # dog-head brand mark + 4-state tray ladder
└── gui/ # Tauri 2 desktop app (system tray + scan UI)
Architecture
houndd (Rust daemon — the engine)
┌──────────────────────────────────┐
│ ScanEngine trait │
│ ├─ L1 ClamAV signatures (now) │
│ ├─ L2 Curated threat packs(Pro)│
│ ├─ L3 Behavioral monitor (Pro) │
│ └─ L4 Supply-chain checks(Pro) │
└──────────────┬───────────────────┘
Unix socket (JSON-RPC, line-delimited)
┌───────────┼───────────┐
hound CLI GUI (Tauri) future modules
The daemon is the only process that touches a scanning engine. CLI and GUI are thin clients — so future suite tools (firewall, updater, …) plug into the same socket.
Swapping the engine (the ClamAV seam)
ClamAV is a temporary dependency. Everything ClamAV-specific — version
probe, signature freshness, the clamscan subprocess + output parsing,
freshclam — lives in one file behind a four-method trait:
crates/houndd/src/engine.rs
trait ScanEngine { name; probe; scan; update }
struct ClamAvEngine // today
const ENGINE: ClamAvEngine // ← flip this line when the native
// engine lands; nothing else in the
// daemon, CLI, GUI, or wire API moves
The wire stays engine-agnostic: Status.engine names the implementation
("clamav" today) and Status.db carries what any signature store has —
a file name and a timestamp. When our own Rust engine ships, it's a new
ScanEngine implementation, a one-const flip, and the tray/CLI/GUI simply
start reporting the new engine name.
Icon system
The brand mark is a solid dog head (assets/icons/hound.svg), a single
flat fill. It ships in two treatments:
- Brand ladder
hound-{16,22,24,32,48,256}.png— native periwinkle#9896E0, for the window icon, About box, and marketing. - Tray-state ladder
state-<name>-{16,22,24,32,48}.png— the same path re-tinted per security state, for the system-tray sentinel:
| State | Fill | Meaning |
|---|---|---|
protected |
#22C55E green |
up-to-date / protected / clean |
scanning |
#F59E0B amber |
scan in progress / signatures need update |
threat |
#EF4444 red |
infection found |
paused |
#6B7280 gray |
real-time monitor off |
Green is the good state; amber is work in progress, never a failure.
Quickstart (development)
Prereqs: Rust (see rust-toolchain.toml), Node 20+, ClamAV, and the
Tauri system libs (libwebkit2gtk-4.1-dev, libgtk-3-dev, libayatana-appindicator3-dev).
# 1. Signatures (needs the clamav freshclam DB)
sudo freshclam
# 2. Daemon (terminal 1)
cargo run -p houndd
# 3. Scan (terminal 2)
cargo run -p hound -- status
cargo run -p hound -- scan ~/Downloads
# 4. GUI
cd gui && npm install && npm run tauri dev
Verifying the engine with the EICAR test file
EICAR is the industry-standard 68-byte test signature — every AV that works will flag it. Generate it and scan it:
printf 'X5O!P%%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*' > /tmp/eicar.com
cargo run -p hound -- scan /tmp/eicar.com
# expect: exit code 1, "Eicar-Test-Signature FOUND"
Updating signatures
hound update wraps freshclam (trying sudo freshclam first, since
plain-user runs can't write /var/lib/clamav and /var/log/clamav). The
GUI's "Update Signatures" button drives the same RPC and shows the log.
cargo run -p hound -- update # or: hound update --json
The GUI (gui/)
A Tauri 2 desktop app — a thin view over the same houndd socket the
CLI uses (via the shared hound-api client), so the window and the
command line never disagree about your security state.
gui/
├── dist/ # the front-end (vanilla HTML/CSS/JS, premium dark shell)
└── src-tauri/ # Tauri 2 shell + system-tray sentinel
The tray sentinel swaps the 4-state icons (green/amber/red/gray) as your state changes; the window shows a live protection hero, a scan progress bar, a results table, and the signature-update log.
Build it:
cd gui
npm install
npm run tauri dev # dev with hot reload
npm run tauri build # → .deb in src-tauri/target/release/bundle/
Git conventions
-
Branch
mainis deployable; small, focused commits. -
No hardcoded secrets. For pushes, the bot token lives in a repo-local credential file (never tracked):
git config credential.helper 'store --file=.git/.git-credentials' chmod 600 .git/.git-credentials echo 'https://<user>:<token>@git.joelovestech.com' >> .git/.git-credentials -
Commits: imperative subject, ≤ 72 chars. Example:
houndd: add line-delimited JSON-RPC socket API
License
TBD — core daemon likely proprietary (freemium), shared CLI possibly OSS.
Decision pending; workspace.package.license = MIT is a placeholder.