HOVEL // chapter 25
Chapter 25 Part 05 / Engineering

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:

  1. hovel module install https://...
  2. hovel module install name or name@version when resolution reaches a configured HTTPS index
  3. hovel module bulk-install https://... or a bulk manifest whose source is a URL
  4. 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:

  1. Use Hovel-managed python-build-standalone by declaring compatible Python versions.
  2. Point at an operator-supplied interpreter path.
  3. 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:

  1. Build and test all Go packages and binaries.
  2. Build and test the Python SDK.
  3. Build the static documentation site from the in-tree HTML sources.
  4. Run Gazelle for Go BUILD file generation.
  5. Run Squatter C formatting, complexity, cppcheck, and clang-tidy checks.
  6. Own generated schemas and contract-test outputs.
  7. 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:

  1. GitHub Releases own native binaries, module packages, checksums, and immutable source archives.
  2. hovel PyPI wheels contain the matching Go binary and launcher.
  3. hovel-sdk PyPI releases contain the Python SDK.
  4. 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.