Configuration and Distribution
Hovel is distributed as a Python-installed Go binary, with modules distributed separately as trusted .tgz packages. Configuration is explicit, schema-validated, offline-ready, and layered in a fixed order.
Installation
The canonical operator install path is PyPI:
pipx install hovel
hovel status
pipx inject hovel hovel-sdk # only needed when authoring Python modules in the same environment
The hovel package contains the platform-specific Go mono-binary and a tiny Python launcher that execs it. It does not download the binary during install or first run. The Python module SDK is a separate PyPI package named hovel-sdk.
GitHub Releases are the canonical artifact source. A release publishes native binaries, PyPI wheels, the SDK wheel/sdist, module packages, module-index.yaml, module-install-set.yaml, and SHA256SUMS. The PyPI projects and module package assets are published from dedicated workflows:
.github/workflows/hovel-pypi.yml
.github/workflows/hovel-sdk-pypi.yml
.github/workflows/modules-release.yml
Config Files
Hovel reads YAML config files. The file extension is .yaml; .yml is accepted for compatibility, but generated examples use config.yaml.
apiVersion: hovel.dev/v1alpha1
kind: HovelConfig
workspace: .hovel
modules:
searchPaths:
- ./modules
- ~/.local/share/hovel/modules
indexes:
- ./module-index.yaml
cache:
enabled: true
runtime:
python:
pythonBuildStandalone:
release: 20260610
logging:
level: info
Precedence is fixed: built-in defaults, then global config, then workspace config, then --config. Maps merge, scalar values override, and lists replace. --config is not repeatable.
$XDG_CONFIG_HOME/hovel/config.yaml
$HOME/.config/hovel/config.yaml
<workspace>/config.yaml
hovel --config ./lab.yaml ...
If a daemon is already running for a workspace and a command supplies a different effective config, Hovel reports the mismatch and asks the operator to restart the daemon. Config hot reload is out of scope for v1.
Offline Rule
Hovel works without an internet connection. No command contacts an internet URL unless the operator explicitly asks for an operation whose source is a URL or managed runtime download.
Explicit internet actions are:
hovel module install https://...hovel module install nameorname@versionwhen resolution reaches a configured HTTPS indexhovel module bulk-install https://...or a bulk manifest whose source is a URL- install-time managed Python setup for a package that declares python-build-standalone and is not run with
--offline
Daemon startup, catalog listing, chain validation, throws, local inspection, and runtime execution never resolve dependencies from the internet.
Module Packages
A module package is a gzip-compressed tar archive with extension .tgz. One package represents one module identity, but it may contain as many host-platform launch variants as the author wants.
ssh-memory-0.1.0.tgz
hovel-module.yaml
bin/
linux-amd64/ssh-memory
darwin-arm64/ssh-memory
windows-amd64/ssh-memory.exe
python/
hovel_ssh_memory/
requirements.txt
vendor/
wheels/
scripts/
setup.py
README.md
LICENSE
hovel-module.yaml is required. Everything else is package-owned. Hovel rejects path traversal during extraction and only allows symlinks that stay inside the package root.
apiVersion: hovel.dev/v1alpha1
kind: ModulePackage
metadata:
name: ssh-memory
version: 0.1.0
moduleType: exploit
summary: SSH memory execution module for authorized labs.
tags: [ssh, lab, dangerous]
author: Example Labs
license: Apache-2.0
homepage: https://example.invalid/hovel/ssh-memory
repository: https://example.invalid/hovel/ssh-memory.git
runtime:
protocol: jsonrpc-stdio
launch:
- selector:
os: linux
arch: amd64
command: ["bin/linux-amd64/ssh-memory"]
- selector:
os: darwin
arch: arm64
command: ["bin/darwin-arm64/ssh-memory"]
- selector:
os: windows
arch: amd64
command: ["bin/windows-amd64/ssh-memory.exe"]
- selector: {}
python:
managed:
versions: ["3.10-3.14"]
requirements: "python/requirements.txt"
wheelhouse: "vendor/wheels"
command: ["{python}", "-m", "hovel_ssh_memory"]
scripts:
postInstall:
command: ["{python}", "scripts/setup.py"]
x-vendor:
anything: allowed
Selectors describe the host running Hovel, not the target being assessed. Platform names use Go names: linux, darwin, windows, amd64, and arm64. Hovel picks the most specific matching launch entry and errors if matching entries tie. If no launch entry matches, install can still succeed, but run and full inspect report the unsupported host and list available selectors.
Install Scripts
Modules are trusted code. Installing a module is a trust decision because the same package can execute arbitrary code when invoked. Hovel therefore supports package scripts and runs declared install scripts by default.
scripts:
preInstall:
command: ["bin/check-host"]
postInstall:
command: ["{python}", "scripts/setup.py"]
preUninstall:
command: ["bin/pre-remove"]
postUninstall:
command: ["bin/post-remove"]
Script commands are argv arrays, not shell strings. They run from the extracted package root with HOVEL_MODULE_ROOT, HOVEL_INSTALL_ROOT, HOVEL_WORKSPACE, and HOVEL_OFFLINE in the environment. --no-scripts exists for recovery and debugging.
Python Runtime
Python package authors have three supported options:
- Use Hovel-managed python-build-standalone by declaring compatible Python versions.
- Point at an operator-supplied interpreter path.
- Bundle an interpreter inside the package and reference it from the manifest.
launch:
- selector: {}
python:
managed:
versions: ["3.10-3.14"]
requirements: "python/requirements.txt"
wheelhouse: "vendor/wheels"
command: ["{python}", "-m", "hovel_ssh_memory"]
- selector: {}
python:
interpreter: "/opt/python/bin/python3"
command: ["{python}", "-m", "hovel_ssh_memory"]
interpreters:
python:
path: "runtime/python/bin/python3"
For managed Python, Hovel pins a known-good python-build-standalone release per Hovel release and downloads the host-platform archive only during explicit install when network use is allowed. The current built-in default is 20260610, and operators may override it with runtime.python.pythonBuildStandalone.release. It creates one virtual environment per installed module version. Dependencies are declared by pointing at requirements.txt and an optional wheelhouse; Hovel does not re-model Python packaging inside its manifest.
Module Install
Modules install from disk, absolute paths, HTTPS URLs, cached packages, configured package roots, index-backed package references, configured indexes, or bulk manifests.
hovel module install ./ssh-memory-0.1.0.tgz
hovel module install /opt/hovel/modules/ssh-memory-0.1.0.tgz
hovel module install https://github.com/vibepwners/hovel/releases/download/v0.3.2/squatter-0.3.2.tgz --sha256 ...
hovel module install ssh-memory
hovel module install squatter@0.3.2
hovel module bulk-install https://github.com/vibepwners/hovel/releases/download/v0.3.2/module-install-set.yaml
hovel module bulk-install ./modules.yaml
hovel module install --link /home/me/dev/my-module
hovel module available
hovel module installed
hovel module uninstall squatter@0.3.2
The default install target is the active workspace. --global installs into the user data directory. A normal install copies or extracts into Hovel-managed storage; --link records an absolute module directory for development.
Installing the same name@version with a different SHA-256 fails unless --replace is supplied. Bulk install accepts local manifest files or HTTPS manifest URLs. Relative sources in a downloaded manifest resolve against the manifest URL. Bulk install runs in order, stops on the first failure, and does not roll back earlier successful installs.
Indexes And Bulk Manifests
Hovel has no default internet index. An operator may configure or pass an index explicitly. Source-tree development can discover local module indexes when HOVEL_MODULE_CONFIG points at an in-repo or private module catalog. In-tree module release packages and indexes are generated with task release:modules-package, which stages declared module binaries into modules/examples/bin/ and writes packages, module-index.yaml, module-install-set.yaml, and SHA256SUMS under dist/modules/. Cached HTTPS indexes are available offline after the first explicit fetch.
apiVersion: hovel.dev/v1alpha1
kind: ModuleIndex
modules:
- name: squatter
version: 0.3.2
url: https://github.com/vibepwners/hovel/releases/download/v0.3.2/squatter-0.3.2.tgz
sha256: ...
apiVersion: hovel.dev/v1alpha1
kind: ModuleInstallSet
modules:
- source: https://github.com/vibepwners/hovel/releases/download/v0.3.2/squatter-0.3.2.tgz
sha256: ...
- source: ./ms17-010-survey-1.0.0.tgz
- source: /opt/hovel/modules/ms17-010-exploit-1.0.0.tgz
Storage
Installed module state is recorded in the workspace lock file, not edited into config. Config describes search paths and policy; install state records the resolved package root, checksum, link mode, and timestamp.
apiVersion: hovel.dev/v1alpha1
kind: ModuleLock
modules:
- name: squatter
version: 0.1.0
sha256: ...
source: /home/me/lab/.hovel/modules/squatter/0.1.0
installedAt: 2026-06-22T16:20:00Z
Default storage uses the active workspace first, then user-level install and cache directories:
<workspace>/modules/
<workspace>/module-lock.yaml
~/.local/share/hovel/modules/
~/.cache/hovel/modules/downloads/
~/.cache/hovel/python-build-standalone/
Downloaded packages are cached by SHA-256 when caching is enabled and cached package archives participate in module available. HTTPS indexes are cached by URL hash after an explicit fetch and can be reused with --offline. Uninstall removes installed records and extracted package state, not cached downloads or shared Python runtimes. Cache cleanup is explicit.
Resolution
module available is local-only discovery: installed records, configured package roots, local .tgz files, cached downloads, and locally reusable index entries. It never contacts the internet. module installed reads completed install records from module-lock.yaml. Module search paths are not recursive. Each search path contains package roots or .tgz files directly. Discovery can list uninstalled packages as available, but execution uses installed or linked packages.
Module references keep the existing name@version form. During install, an exact name@version or bare name checks cached package archives first, then configured package roots and archives, then the source-tree module index when present, then configured or explicit indexes. Package roots install as links; archives and URLs install into managed workspace storage. Bare install references choose the newest semver package in the first matching source tier. During execution, bare name resolves to the highest semver installed version. If duplicate candidates remain after version and precedence resolution, Hovel errors instead of guessing. Workspace-installed modules beat global modules and cache entries.
Chains never auto-download missing modules. If a configured index knows a missing module, the error may include the install command.
Build Strategy
Hovel is a monorepo because in-tree modules, services, SDKs, schemas, CLI, TUI, API, and MCP adapters evolve together.
Taskfile.yml is the authoritative build, test, lint, docs, run, and release entry point. The tasks may call Bazel or other tools internally, but contributors, CI, hooks, docs, and automation use task <name> rather than invoking Bazel, gofmt, uv, or lefthook directly.
Task/Bazel responsibilities:
- Build and test all Go packages and binaries.
- Build and test the Python SDK.
- Build the static documentation site from the in-tree HTML sources.
- Run Gazelle for Go BUILD file generation.
- Run Squatter C formatting, complexity, cppcheck, and clang-tidy checks.
- Own generated schemas and contract-test outputs.
- Produce release archives, PyPI wheels, and module packages.
The current repository contract is, with Task as the single entry point:
task build
task test
task fmt # wired formatters: core Go/Gazelle plus Go SDK
task check # checks available in this checkout
task ci # full-checkout guard plus core, SDK, modules, and docs gates
Taskfile.yml is the one correct way to build, test, lint, format, and run; contributors, CI, and tooling never invoke Bazel, gofmt, uv, or lefthook directly. The root task file is a sparse-checkout tolerant dispatcher. Core work delegates into the self-contained core/ workspace; SDK, module, and docs checks use the root integration workspace so a full checkout can validate demos, module packages, SDKs, and documentation without making those trees part of core/.
Distribution
The released Go mono-binary is still available directly from GitHub Releases:
hovel-linux-amd64
hovel-linux-arm64
hovel-darwin-arm64
hovel-darwin-amd64
hovel-windows-amd64.exe
The PyPI package is the primary install facade for operators:
uvx hovel throw ./ssh-memory.chain.yaml
uvx hovel throw ./ssh-memory.chain.yaml --now
The package includes no modules by default. In-tree modules such as MS17-010, Squatter, and the mock modules are built as separate .tgz artifacts with their module-declared versions and published on the GitHub Release page. Current compiled module packages advertise Linux and Darwin launchers; the Windows Squatter payload is validated through the Wine-backed Squatter task and demo path until Windows compiled module launchers are restored.
Release artifact authority:
- GitHub Releases own native binaries, module packages, checksums, and immutable source archives.
hovelPyPI wheels contain the matching Go binary and launcher.hovel-sdkPyPI releases contain the Python SDK.- Hovel and Hovel SDK versions are released together. Module package versions come from each module's descriptor; third-party module versions remain independent.
Package names may include version and platform for clarity, such as squatter-0.3.2.tgz or squatter-0.3.2-linux-amd64.tgz, but the manifest is authoritative.
Terminal Libraries And Theming
The interactive cli shell should use go-prompt for prompt input, history, suggestions, and completions. The cli shell and TUI should share a small theme system without making theme work part of the critical path. Lip Gloss is the styling engine for terminal presentation, with shared tokens for colors, borders, severity, focus, muted text, success, warning, danger, and active throw state.
Initial theme names:
operator
acid
bloodmoon
ghost
crt
paperhovel
midnight
amberterm
The visual target is a readable operator console with high contrast, tight navigation, and clear live status. The look should feel distinctive, but output must remain usable in low-color terminals, over SSH, in logs, and with --no-color. Animation should clarify state changes, not compete with the work.