Skip to content

Dynamic Inventory

github-actions[bot] edited this page Oct 11, 2026 · 3 revisions

Dynamic inventory

Dynamic inventory (Proxmox / Hetzner / DO / Linode / Vultr / Scaleway / EC2 / Ansible)

A dynamic folder auto-populates its contents from an external provider on a refresh interval. The entries inside aren't stored as connection rows in the DB - they're a live cache. Connecting to an entry constructs an ephemeral connection in memory, inherits the dynamic folder's credentials / jump host / SSH options, and runs through the standard SSH layer.

Creating a dynamic folder

  • Right-click a parent folder (or empty tree area) → New dynamic subfolder… Pick a provider, fill in the config, save.
  • Required: an API token credential stored in the vault. The folder editor has an inline "+ New token credential" form so you don't have to switch tabs.

Providers

Proxmox VE. Config: base URL (the load-balancer URL works fine - /cluster/resources returns the whole cluster regardless of which node answers), API token id (user@realm!tokenid), and the token secret credential. Filter: include hosts (Proxmox nodes), include guests (VM + LXC), tag whitelist/blacklist, hide-stopped. The hostname for SSH defaults to the VM name on the assumption that DNS resolves it.

Hetzner Cloud. Config: API token credential, hostname source (name / public IPv4 / private IPv4 - pick what reaches the server from where you sit), label whitelist/blacklist, hide-stopped. Hetzner has no DNS auto-record, so the hostname source picker is load-bearing.

DigitalOcean. API token credential + hostname source (name / public IPv4 / private VPC IPv4). Read-scope token from Cloud → API → Tokens & Keys is enough. Droplet tags map straight to the filter tag vocabulary.

Linode (Akamai Cloud Compute). API token credential + hostname source (label / public IPv4 / private IPv4). Read-only Linodes scope. Tags include a synthetic region=<region> plus the user's own tags. Private-IP detection uses RFC1918 ranges + Linode's 192.168.128/17 private block.

Vultr. API token credential + hostname source (label / main public IPv4 / internal IPv4). Read-access token. Tags include a synthetic region=<region>.

Scaleway. API token credential + zone (required; e.g. fr-par-1, nl-ams-1, pl-waw-1) + hostname source. One folder per zone - the API scopes server listings by zone.

AWS EC2. Reuses the api_token credential shape: the credential's token id field holds the access key, the secret holds the secret access key. Folder config also needs the region (eu-central-1 etc.); one folder per region. Hostname sources include EC2-specific options (Name tag, Public DNS, public IPv4, private IPv4). The Name tag is the default. Tags include a synthetic region=<region>. Requires IAM permission ec2:DescribeInstances. SigV4 is signed inline so there's no AWS SDK dependency.

Ansible static inventory. Reads a local .ini or .yml inventory file (format auto-detected by extension). Config:

  • Inventory file path - absolute path; Browse… button opens a native file picker.
  • Host pattern / Group pattern - optional fnmatch globs (web*, prod_*).
  • Display name - inventory_hostname (default) or ansible_host value if you'd rather see IPs in the tree.
  • Jump host credential - SSH-capable credential (password / key / agent / opkssh) applied to every hop parsed out of ansible_ssh_common_args. Target host credentials almost never work on the bastion; without this picker connects through a jump host will fail authentication.

What gets lifted from the inventory at connect time:

Ansible var Becomes
ansible_host actual SSH dial target (overrides hostname)
ansible_user per-host username override
ansible_port per-host port override
ansible_ssh_common_args / _extra_args jump chain (-J, ProxyJump=, ProxyCommand=ssh -W all recognised)

Each Ansible group the host belongs to becomes a tag - group filter in the sidebar narrows to hosts carrying it, and Ctrl+K matches tags too. Folder structure stays flat (every host as a single entry); we don't mirror Ansible's DAG.

Per-connect overrides via the entry's detail pane (Use different credential…): credential, username, password, jump host ([user@]host[:port] - replaces the entire parsed chain) and jump credential (one-off override of the folder-level picker). All fields reset after Connect.

The five non-Hetzner cloud providers were added together and have not yet been tested end-to-end against real accounts. Please file a bug if your provider misbehaves - the per-provider Fetch path is small and easy to inspect.

Visual cues in the tree

  • Globe icon instead of folder.
  • Folder name + icon tinted teal so dynamic folders stand out.
  • Provider pill (proxmox / hetzner / ansible / …) inline next to the name.
  • Red ! dot when the last refresh errored - hover for the message.
  • Tooltip on the name shows provider + "refreshed Nm ago" + last error if any.
  • Count badge shows cached entry count (or hidden if empty).
  • Per-entry icon based on kind: VM → Monitor, LXC → Box, host / server → Server tower. Consistent across the tree, Ctrl+K palette, and the detail-pane header.

Inspecting an entry

Click a VM / LXC / server / node to open its detail pane. The pane is read-only - the source of truth is the provider, not the local DB.

  • Header: name + status pill + provider chip + folder breadcrumb.
  • Actions: Connect (with stopped-VM confirm), Copy host, Refresh inventory.
  • Facts grid:
    • Proxmox: resource type, hosting node, VMID, vCPUs, memory, disk, uptime.
    • Hetzner: server id, server type, datacenter, image, public IPv4 / IPv6, private IPv4, created.
    • DigitalOcean: droplet id, public IPv4, private IPv4.
    • Linode: linode id, region, public IPv4, private IPv4.
    • Vultr: instance id, region, main IPv4, internal IPv4.
    • Scaleway: server id, public IPv4, private IPv4.
    • AWS EC2: instance id, public / private IPv4, public DNS.
  • Tags / labels row.
  • Live load bars (CPU / Memory / Disk) for Proxmox, colour- coded green<60 / yellow<85 / red≥85. Hetzner doesn't expose live load on the /servers endpoint, so the section is hidden.
  • Collapsible Raw provider payload with the JSON for everything not covered above.

Connecting

  • Double-click an entry in the tree (or click Connect in the detail pane) to start an SSH session.
  • The session inherits the dynamic folder's credentials, jump host, and SSH options - same cascade as a regular tree branch.
  • A stopped entry triggers a confirm dialog ("connect anyway?") in case the VM is reachable on another address or the status is stale.
  • The session's connection id is dyn:<entryID>. It doesn't appear as a row in any connection list - it lives in the session pool until disconnected.
  • A failed connect surfaces inline in the entry's detail pane (red row above the facts grid). The previous alert() modal with the wails.localhost says title is gone.

Bulk actions

  • Ctrl-click entries to toggle them in / out of a multi- selection set; Shift-click selects a range. The detail pane switches to a bulk view showing the count and two actions:
    • Connect all opens one terminal tab per selected entry in parallel.
    • Batch exec… runs a single command across the whole selection and shows per-host stdout / stderr / exit in a single modal (same component the regular multi-connection batch panel uses; results expanded by default).
  • The Batch exec modal also has a Load snippet… picker so you can pull a saved global snippet straight into the command field before pressing Run.

One-shot credential / username / password override

Same gesture as on regular connections: Use different credential… next to Connect opens an inline block with three independent override fields. Resets after the next press; nothing persists. Useful for hitting a fresh dynamic host with a different identity without editing the folder's inherited credential.

Refresh behaviour

  • The folder refreshes on its configured interval (default 60s).
  • Manual refresh via right-click → Refresh now or the button in an entry's detail pane.
  • A refresh error leaves the previous entries cached (last good state) and surfaces the error via the red ! dot.

Editing the config

Right-click a dynamic folder → Edit dynamic config… Same form as creation. Changing the refresh interval restarts the timer.

Pinning a dynamic host as a permanent connection

Some hosts in a dynamic inventory don't really need to be dynamic - they don't move, you've got custom notes for them, and you want them to survive even if the API token gets revoked or the provider rotates the IP. Click the host in the tree to open the detail pane, then Pin as connection…. The host becomes a real connection inside the same folder (any per-attempt credential override you picked becomes the new connection's default credential; Ansible vars like ansible_user and the parsed jump chain are carried over). Future inventory refreshes skip that host's external ID so it doesn't appear twice - once as the real connection, once as a dynamic ghost.

To unpin: just delete the pinned connection like any other. The next inventory refresh re-includes the host as a dynamic ghost.

Converting a whole dynamic folder to static

When the source of truth stops changing - you got handed a one-off Ansible inventory, or you're retiring an API token but want to keep the snapshot - open the dynamic folder editor and click Convert to static…. Every cached host becomes a regular connection in the folder, the provider link is dropped, the refresh timer stops, and the folder behaves like any other folder you created by hand. Irreversible from the UI; existing pinned connections in the same folder are untouched.

Clone this wiki locally