2026-09-12 15:57:53 +03:30

sshctl

SSH configuration management toolkit — parse, validate, generate, and manage SSH configs at scale.

Python License: MIT


Features

  • Parse — Full SSH config parser supporting Host, Include, glob patterns, and continuation lines
  • Generate — Programmatic SSH config generation with deterministic option ordering
  • Validate — Validate SSH config structure, check required options and missing includes
  • Check — Parallel DNS/TCP/SSH connectivity checking across all hosts
  • Exec — Run commands concurrently across all hosts via SSH
  • Export — Export host inventory to CSV
  • Ansible — Generate Ansible inventory from your SSH config directory tree
  • Permissions — Fix or verify SSH directory permissions (0700 dirs, 0600 keys, 0644 public keys)
  • CLI — Full-featured typer CLI with rich output (tables, trees, status)

Installation

pip (wheel)

pip install sshctl # once published to PyPI
# or install the built wheel directly from a release
pip install ./sshctl-0.1.2-py3-none-any.whl

dpkg (Debian/Ubuntu)

# Download from latest release
wget https://src.panahifar.ir/ahp/sshctl/releases/download/v0.1.2/sshctl_0.1.2-1_all.deb
sudo dpkg -i sshctl_0.1.2-1_all.deb

From source

git clone https://src.panahifar.ir/ahp/sshctl.git
cd sshctl
pip install -e .

Download the latest .whl or .deb from the releases page.


Quick Start

# Initialize SSH config directory structure
sshctl init

# List all hosts
sshctl host list

# Show a specific host
sshctl host show server1

# Validate config
sshctl validate

# Check host connectivity
sshctl check

# Run a command across all hosts in parallel
sshctl exec "uptime"

# Export inventory as CSV
sshctl csv --output hosts.csv

# Generate Ansible inventory
sshctl ansible

# Fix SSH directory permissions
sshctl chmod

# Only check permissions (no fixes)
sshctl chmod --check

# Show conf.d directory tree
sshctl tree

Directory Structure

~/.ssh/
├── config              # Main SSH config (Include conf.d/)
├── conf.d/             # Modular host configurations
│   ├── internal/       # Internal infrastructure universe
│   │   ├── AZ/         # Sector (e.g., region)
│   │   │   └── cluster1/
│   │   │       └── 8xx-serverfarm/
│   │   │           └── 82-k8s/
│   │   │               └── hosts.conf
│   │   └── ...
│   └── external/       # External/dedicated universe
│       └── ...
├── control.d/          # SSH control master sockets
├── keys.d/             # Private keys (auto-protected by sshctl chmod)
├── keys.pub/           # Public keys
├── report/             # Generated reports
└── inventory.d/        # Generated Ansible inventory
    └── hosts.yml

ZonePath Convention

The conf.d/ hierarchy follows a ZonePath addressing model:

conf.d/{universe}/{sector}/{cluster}/{zone}/{vlan}
Level Description Example
universe internal or external internal
sector Geographic/segment AZ
cluster Data center / DC cluster1
zone Server farm / role 8xx-serverfarm
vlan Network segment 82-k8s

CLI Reference

Global Options

Flag Description
-V, --version Show version
-v, --verbose Verbose logging
-c, --config Path to SSH config file

Commands

Command Description
init Initialize SSH config directory structure
host list List all host aliases
host show Show details for a specific host
host add Add a new host to the SSH config
check Check connectivity to all hosts
exec Run a command in parallel across hosts
csv Export host inventory to CSV
chmod Fix or check SSH directory permissions
validate Validate SSH config structure
diff Show differences between generated and current config
backup Create timestamped backup of SSH config directory
restore <path> Restore SSH config from a backup
ansible Generate Ansible inventory from SSH config
tree Show the conf.d directory tree
install-completion Install shell completions (bash/zsh/fish)

Python API

from pathlib import Path
from sshctl.parser import parse_file, resolve_includes
from sshctl.models import HostBlock, SshConfig
from sshctl.generator import generate_config_text, write_config_file
from sshctl.validator import validate_config
from sshctl.resolver import resolve_host, resolve_all_hosts
from sshctl.exporter import export_csv
from sshctl.permissions import fix_ssh_permissions, check_permissions
from sshctl.inventory import generate_inventory

# Parse an SSH config
config = parse_file(Path("~/.ssh/config").expanduser())

# Resolve includes recursively
full_config = resolve_includes(Path("~/.ssh/config"))

# Find hosts
block = config.find("server1")
blocks = config.find_all("web*")

# Validate
result = validate_config(Path("~/.ssh/config"))
if not result.passed:
    for issue in result.issues:
        print(f"[{issue.severity}] {issue.message}")

# Resolve a host (uses ssh -G)
resolved = resolve_host("server1", Path("~/.ssh/config"))

# Generate config text
text = generate_config_text(full_config)

# Generate Ansible inventory
inventory = generate_inventory(Path("~/.ssh"))

# Fix permissions
counts = fix_ssh_permissions(Path("~/.ssh"))

Configuration

sshctl can be configured via YAML or environment variables.

YAML (~/.ssh/sshctl.yaml)

ssh_dir: ~/.ssh
config_file: config
verbose: false
timeout: 10

Environment Variables

All settings can be overridden with SSHCTL_ prefix:

export SSHCTL_VERBOSE=true
export SSHCTL_TIMEOUT=30

Development

Setup

git clone https://src.panahifar.ir/ahp/sshctl.git
cd sshctl
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

Run Tests

pytest

Lint & Type Check

ruff check src/ tests/
ruff format --check src/ tests/
mypy src/ tests/

Project Structure

sshctl/
├── src/
│   └── sshctl/
│       ├── __init__.py          # Package init, re-exports from core
│       ├── __main__.py          # Entry point for python -m sshctl
│       │
│       ├── cli/                 # CLI layer (user-facing)
│       │   ├── __init__.py
│       │   ├── app.py           # Typer CLI application
│       │   └── __main__.py      # Module entry point
│       │
│       ├── core/                # Core domain (shared foundation)
│       │   ├── __init__.py      # Exports __version__
│       │   ├── _version.py      # Version string
│       │   ├── config.py        # Pydantic settings (SshctlConfig)
│       │   ├── exceptions.py    # Exception hierarchy
│       │   ├── logutil.py       # Structured logging (structlog)
│       │   └── models.py        # Data models (HostBlock, SshConfig, ZonePath)
│       │
│       ├── config/              # SSH config lifecycle
│       │   ├── __init__.py
│       │   ├── parser.py        # SSH config parser + include resolution
│       │   ├── generator.py     # SSH config text generation
│       │   ├── resolver.py      # Host resolution via ssh -G
│       │   └── validator.py     # Config validation
│       │
│       ├── operations/          # Active operations
│       │   ├── __init__.py
│       │   ├── checker.py       # DNS/TCP/SSH connectivity checks
│       │   └── executor.py      # Parallel SSH command execution
│       │
│       └── export/              # Export/integration
│           ├── __init__.py
│           ├── exporter.py      # CSV export
│           ├── inventory.py     # Ansible inventory generation
│           └── permissions.py   # SSH directory permissions
│
├── tests/
│   ├── conftest.py              # Pytest fixtures
│   ├── test_config.py
│   ├── test_exceptions.py
│   ├── test_exporter.py
│   ├── test_generator.py
│   ├── test_inventory.py
│   ├── test_models.py
│   ├── test_parser.py
│   ├── test_permissions.py
│   └── test_validator.py
│
├── pyproject.toml               # Build config, ruff, mypy, pytest
├── .editorconfig
├── .gitignore
├── LICENSE
└── README.md

Architecture

sshctl follows a modular layered architecture organized into subpackages:

┌─────────────────────────────────────────────────────────────┐
│                    cli/ (app.py)                            │
│              Typer CLI, Rich output, orchestration          │
└──────────────────────┬──────────────────────────────────────┘
                       │
          ┌────────────┼────────────────┬────────────────┐
          ▼            ▼                ▼                ▼
   ┌──────────┐ ┌───────────┐ ┌──────────────┐ ┌──────────────┐
   │ config/  │ │operations │ │   export/    │ │    core/     │
   │          │ │           │ │              │ │              │
   │ parser   │ │ checker   │ │ exporter     │ │ models       │
   │ generator│ │ executor  │ │ inventory    │ │ exceptions   │
   │ resolver │ │           │ │ permissions  │ │ config       │
   │ validator│ │           │ │              │ │ logutil      │
   └──────────┘ └───────────┘ └──────────────┘ └──────────────┘
                                                    ▲
                                                    │
                                            (shared foundation)

Key design decisions:

  • Models first — HostBlock, SshConfig, ZonePath are plain dataclasses; the entire pipeline operates on them
  • Parser is lossy by design — it extracts enough structure for management, not a perfect SSH config round-trip
  • Resolver delegates to ssh -G — rather than reimplementing SSH's full inheritance logic, sshctl shells out for the canonical resolved view
  • Async everywhere — checker and executor use asyncio with semaphore-controlled concurrency

License

MIT — see LICENSE

S
Description
SSH configuration management toolkit
Readme MIT
125 KiB
v0.1.2
Latest
2026-09-12 12:36:30 +00:00
Languages
Python 98.3%
Makefile 1.1%
Shell 0.6%