Skip to content

bthall/mox

Repository files navigation

mox logo

mox

A CLI tool for creating and managing tmux sessions declaratively from YAML configuration files.

Features

  • Declarative YAML config — one window per host, full custom layouts, or anything between
  • Cssh-style broadcastsync: true for synchronized typing across panes; tiled layouts
  • Ad-hoc multi-host sessionsmox new @cluster or mox new host1 host2 host3 without editing config
  • Cluster expansion@name resolves to a configured session's hosts or a clusterssh clusters file entry (with nested-cluster expansion)
  • Import existing tmux sessions — capture a running session's structure into the config (mox import)
  • Local quick sessionsmox new with no args opens a single-pane local session; -t makes it self-destruct on detach
  • In-tmux modemox new -w @cluster opens a window in your current session instead of a new session
  • Configurable connect — defaults to ssh {{host}}; override per session/window; ssh_user: shortcut
  • Reusable named layouts — define once, reference from any window
  • Shell completion — bash, zsh, fish; completes sessions, clusters, layouts, and running tmux sessions
  • Strict validation — typos in config keys error with line numbers
  • Honest defaults — single binary, no daemon, no state

Install

From source

git clone https://github.com/bthall/mox.git
cd mox
make install        # installs binary to $GOPATH/bin + shell completion for $SHELL

make install also installs completion for your current shell (bash, zsh, or fish). If you only want completion (e.g. after go install), run make install-completion.

Using go install

go install github.com/bthall/mox/cmd/mox@latest

(go install requires that the module has a published tag — until the first release, build from source.)

Pre-built binaries

Once a release is cut, archives for linux/macOS × amd64/arm64 will be attached to the GitHub Releases page along with checksums.txt.

Quick start

mox init                        # scaffold a default config at ~/.config/mox/config.yml
mox -a example                  # build + attach to the "example" session
mox list                        # configured + running tmux sessions
mox kill example                # destroy a running session

Recipes

Common workflows by example. Every short flag has a long form (mox new --help for the full table).

Attach to a configured session

mox -a dev                      # build it if it isn't running, then attach
mox -a dev --force              # tear down the running one and rebuild

-a also attaches to any running tmux session, even ones not in your mox config — useful for hand-rolled sessions you started by hand.

Quick local session

mox new                         # plain shell in a new tmux session (auto-named tmp-<timestamp>)
mox new -n work                 # named "work"
mox new -n work -r ~/proj       # named "work", starting in ~/proj
mox new -t                      # destroyed automatically when you detach

Multi-host admin (cssh-style)

mox new with hosts gives you a tiled grid, synchronized typing across panes, and sudo -i auto-sent so you type the root password once.

mox new host1 host2 host3       # 3 hosts, broadcast typing, sudo on connect
mox new @rps-api                # same, using a configured session's host list
mox new -u root @rps-api        # ssh as root
mox new -S=false @rps-api       # turn off broadcast typing for this one
mox new --sudo=false @rps-api   # skip sudo

Open as a window in your current tmux session

If you're already inside tmux and don't want a separate session:

mox new -w @rps-api             # new window in the current session

Clone a configured session, override hosts

mox new --from rps-api host42   # use rps-api's settings, but just this one host

Capture a hand-rolled tmux session into config

If you built a session interactively and want to recreate it later:

mox import work                 # add 'work' to your mox config under the same name
mox import work -n my-work      # rename on import
mox import work -p              # preview the YAML on stdout without saving
mox import work -F              # overwrite an existing config entry

Per-pane commands aren't recoverable from tmux's running state (send-keys is one-way) — the imported session is structure-only. Add commands: entries afterward to make it fully reproducible.

Migrate from clusterssh

If you already maintain ~/.clusterssh/clusters, you don't have to duplicate it. mox reads that file directly:

mox new @rps-monitoring         # expands the clusterssh entry by that name
mox new @pve                    # nested clusterssh tags work too

Configured sessions in ~/.config/mox/config.yml take precedence when a name exists in both places.

Tab completion

After make install, <TAB> works for every argument that takes a name:

mox <TAB>                       # subcommands
mox -a <TAB>                    # configured + running tmux sessions
mox new @<TAB>                  # cluster names (config + clusterssh)
mox new --arrange <TAB>         # tmux layout names
mox kill <TAB>                  # running tmux sessions

Configuration

The default config path follows the XDG Base Directory spec:

  • $XDG_CONFIG_HOME/mox/config.yml if set, otherwise
  • ~/.config/mox/config.yml

Override via the --config flag (which expands ~).

The file is created with mode 0600 (owner read/write only) since it can list hostnames and shell commands.

Simple session

One window, one pane per host:

sessions:
  devenv:
    hosts: [api, web, worker]
    root: ~/projects/myapp
    commands:
      - echo "Welcome!"

Custom connect command

The default connect template is ssh {{host}}. Override per session or per window with the connect: field, or use the ssh_user: shortcut to prefix the default with USER@:

sessions:
  prod:
    connect: "ssh -p 2222 ops@{{host}}"
    hosts: [api1, api2]

  as-root:
    ssh_user: root                    # same as connect: "ssh root@{{host}}"
    hosts: [a, b, c]

  mixed:
    windows:
      - name: shells
        connect: "mosh {{host}}"      # window-level override
        hosts: [bastion]
      - name: local
        hosts: [localhost]            # uses default ssh

Synchronized panes and tiled layouts (cssh-style)

For multi-host administration where you want one keystroke to broadcast to every pane:

sessions:
  mongo-cluster:
    sync: true                        # synchronize-panes on
    arrange: tiled                    # grid layout instead of vertical strips
    hosts: [mongo-1, mongo-2, mongo-3]
    commands:
      - sudo -i                       # password typed once, applied to all

arrange: accepts any of tmux's built-in layouts: tiled, even-horizontal, even-vertical, main-horizontal, main-vertical. Both sync: and arrange: work at the session and window level (window overrides session).

Multiple windows

sessions:
  staging:
    root: ~/staging
    windows:
      - name: backend
        hosts: [api1, api2, api3]
      - name: frontend
        hosts: [web1, web2]

Custom layout

split: root marks the first pane (no actual split — it's the pane that exists when the window is created). split: horizontal stacks the new pane under the previous; split: vertical places it side-by-side. size: is a percentage of the parent pane (1–99).

sessions:
  monitoring:
    windows:
      - name: system
        panes:
          - split: root
            commands: [htop]
          - split: horizontal
            size: 30
            commands: [df -h]

Reusable layouts

layouts:
  two-pane:
    panes:
      - split: root
      - split: vertical
        size: 30

sessions:
  dev:
    windows:
      - name: logs
        layout: two-pane

See examples/config.yml for more.

Commands

Session lifecycle:
  mox -a <session>      attach to a configured session (builds it if not running)
                        also attaches to any running tmux session by name
  mox new [hosts...]    ad-hoc session or window (alias: cssh)
  mox list | ls         list configured and running sessions
  mox kill <session>    destroy a running session
  mox import <session>  capture a running tmux session into the config

Configuration:
  mox init              scaffold a default config
  mox validate          check config syntax
  mox config path       print resolved config path
  mox config view       print the raw config file

Other:
  mox completion <sh>   emit a shell-completion script
  mox --version         print the build version

mox <command> --help shows the full flag list for any command, including shorthands and defaults.

Cluster expansion (@name)

Anywhere mox new accepts a host, @name expands to a host list. Lookup order:

  1. The hosts: of a mox configured session named name (complex sessions are flattened across all their simple-mode windows)
  2. The name entry in ~/.clusterssh/clusters (or $CSSH_CLUSTERS, or /etc/clusters). Nested clusters are expanded recursively with cycle detection.

When name exists in both, the mox config wins. Mix freely with literal hosts on the same line.

Shell completion

make install does this automatically for your $SHELL. To install it standalone, or after go install:

make install-completion         # detects $SHELL, installs to user dir

# or manually:
mox completion bash > ~/.local/share/bash-completion/completions/mox
mox completion zsh  > ~/.zsh/completions/_mox
mox completion fish > ~/.config/fish/completions/mox.fish

Global flags

  • -c, --config <path> — override the config path (~ expanded)
  • -v, --verbose — debug logging to stderr
  • -q, --quiet — only warnings and errors
  • --forcemox -a <session> --force rebuilds the session even if it's running

Behavior notes

When -a attaches from inside an existing tmux client ($TMUX set), mox uses switch-client instead of attach-session, so nested-session errors are avoided automatically.

Ctrl-C during session creation cancels cleanly: the partial session is killed before the program exits.

Validation rules

  • Session and window names cannot contain : . $ or whitespace (these are special in tmux's target syntax)
  • Hostnames in the default ssh {{host}} template must match ^[A-Za-z0-9._%@-]+$. To use other characters, override connect: with a template that handles your own escaping
  • Unknown YAML keys are an error (catches typos like hots: for hosts:)
  • A session can define hosts, windows, or neither (a session with neither opens a single local pane — useful for commands:-only workflows)
  • Inside a window, hosts is mutually exclusive with panes and layout, and panes is mutually exclusive with layout
  • Multiple windows in a session may share the same name (tmux addresses windows by id, not name)

Security

The config file is treated as trusted user input — commands: and the connect: template are executed in the spawned tmux pane via send-keys. Sharing a config is equivalent to sharing arbitrary shell commands.

See SECURITY.md for vulnerability reporting.

Development

make build         # build to ./build/mox
make test          # unit tests
make integration   # tests requiring a live tmux server
make lint          # golangci-lint
make vuln          # govulncheck scan

CI runs the unit tests + lint + govulncheck on every push, plus integration tests on Linux. See CONTRIBUTING.md for details.

License

MIT

About

Declarative tmux session manager with cssh-style multi-host broadcast and cluster expansion.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors