Skip to content

Inventory — setup, creation & overrides

tunnel-manager works from a single YAML inventory that maps short host aliases (for example, managed-node) to connection metadata. Every entry point — the HostManager Python API, the tunnel-manager CLI, the tunnel-manager-mcp MCP server, and container-manager-mcp and the ssh-bootstrap skill — reads the same file, so you define your fleet once.

Default location

$XDG_CONFIG_HOME/agent-utilities/inventory.yml      # preferred
$XDG_CONFIG_HOME/agent-utilities/inventory.yaml     # legacy fallback

which is ~/.config/agent-utilities/inventory.yml on a typical Linux/macOS host (XDG_CONFIG_HOME defaults to ~/.config). The path is resolved in this order:

  1. inventory.yml if it exists — the standard for fresh installs, else
  2. inventory.yaml if it exists — the legacy filename, still fully supported, else
  3. inventory.yml — the path a fresh init writes to.

So existing .yaml users keep working untouched, while new installs standardize on .yml. If neither file exists, tunnel-manager starts with an empty inventory (no error) and you can add hosts via the API/CLI.

Note: this is the agent-utilities config directory on purpose — the inventory is shared across the ecosystem. Earlier builds of the MCP server defaulted to ~/.config/tunnel-manager/inventory.yml; that has been unified to the path above.

Create your inventory

The fastest start is the init command, which writes a fully-commented template to the resolved path:

tunnel-manager inventory init        # writes ~/.config/agent-utilities/inventory.yml
$EDITOR ~/.config/agent-utilities/inventory.yml
tunnel-manager inventory doctor      # validate it

init refuses to clobber an existing file unless you pass --force, and creates the parent directory for you. The template documents every supported host field with example hosts and group structure (reproduced under Template below).

Inventory CLI commands

Command What it does
tunnel-manager inventory init [--inventory PATH] [--force] Write the commented inventory.yml template to the resolved path (or --inventory). Refuses to overwrite without --force.
tunnel-manager inventory doctor [--inventory PATH] [--fix] Validate: file exists + parses, each host has required fields, groups reference real hosts. Exits non-zero on hard errors. With --fix, migrate a legacy inventory.yaml to inventory.yml.
tunnel-manager inventory show [--inventory PATH] Print the resolved inventory path plus a host/group summary.

Template

This is the copy-paste template tunnel-manager inventory init writes (comments trimmed here for brevity):

all:
  vars:
    ansible_user: operator
    ansible_ssh_private_key_file: ~/.ssh/id_rsa
    ansible_ssh_known_hosts_file: ~/.ssh/known_hosts
  hosts:
    managed-node:
      ansible_host: 192.0.2.13
    managed-node-secondary:
      ansible_host: 192.0.2.16
      ansible_user: ml
      ansible_port: 2222
  children:
    storage:
      vars:
        ansible_user: admin
      hosts:
        storage-node:
          ansible_host: 192.0.2.10
          # ansible_ssh_pass_ref: vault://ssh/storage/password
          # ansible_ssh_common_args: "-J jump@bastion"

Format

Two layouts are accepted:

Ansible-style (recommended) — supports group-level vars and children groups:

all:
  vars:
    ansible_user: operator
    ansible_ssh_private_key_file: ~/.ssh/id_rsa
    ansible_ssh_known_hosts_file: ~/.ssh/known_hosts
  hosts:
    managed-node:
      ansible_host: 192.0.2.13
    managed-node-secondary:
      ansible_host: 192.0.2.16
      ansible_user: ml
      ansible_port: 2222
  children:
    storage:
      hosts:
        storage-node: { ansible_host: 192.0.2.10 }

Flat — top-level keys are aliases (no all: wrapper):

managed-node:
  hostname: 192.0.2.13
  user: operator
  identity_file: ~/.ssh/id_rsa
  known_hosts_file: ~/.ssh/known_hosts

Recognized per-host keys

Ansible alias Native key Meaning
ansible_host hostname IP / DNS name (defaults to the alias).
ansible_user user SSH user.
ansible_port port SSH port (default 22).
ansible_ssh_private_key_file identity_file / key_path Path to the private key.
ansible_ssh_pass_ref password_ref Secret-manager reference for password auth.
ansible_ssh_known_hosts_file known_hosts_file Verified SSH server-key trust store.
ansible_ssh_common_args proxy_command Extra SSH args / jump host.

Group-level vars apply to all hosts in the group; per-host values override them.

Build it programmatically instead

from tunnel_manager.tunnel_manager import HostManager

hm = HostManager()                      # uses the default path
hm.add_host(
    "managed-node",
    hostname="192.0.2.13",
    user="operator",
    identity_file="~/.ssh/id_ed25519",
    known_hosts_file="~/.ssh/known_hosts",
)
hm.save_inventory()                     # writes back to the inventory file

Override the default location

Surface How
CLI tunnel-manager --inventory /path/to/inventory.yml <subcommand>
MCP server export TUNNEL_INVENTORY=/path/to/inventory.yml (or pass the inventory arg to tm_inventory)
Python API HostManager(config_file="/path/to/inventory.yml")

The MCP tm_inventory tool also takes an optional group arg (default all) to scope operations to one Ansible group; the env equivalent is TUNNEL_INVENTORY_GROUP.

Use it: SSH mesh bootstrap

Before any connection, provision each server's verified public host key in the configured known_hosts file. Tunnel Manager never accepts an unknown key and never treats ssh-keyscan output as trusted. Once that trust input exists, key authentication can be distributed across the fleet:

# CLI
tunnel-manager setup-all --parallel
// MCP (tm_inventory)
{ "action": "mesh_bootstrap" }   // inventory defaults to the XDG path above

This operation distributes authentication keys; it does not manufacture host-key trust. Each peer still needs a governed known_hosts entry before it can connect. Remote command execution, rotation, and config copy then resolve inventory aliases.