# sshctl SSH configuration management toolkit — parse, validate, generate, and manage SSH configs at scale. [![Python](https://img.shields.io/badge/python-3.13+-blue.svg)]() [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) --- ## 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) ```bash 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) ```bash # 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 ```bash git clone https://src.panahifar.ir/ahp/sshctl.git cd sshctl pip install -e . ``` Download the latest `.whl` or `.deb` from the [releases page](https://src.panahifar.ir/ahp/sshctl/releases). --- ## Quick Start ```bash # 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 ` | 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 ```python 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`) ```yaml ssh_dir: ~/.ssh config_file: config verbose: false timeout: 10 ``` ### Environment Variables All settings can be overridden with `SSHCTL_` prefix: ```bash export SSHCTL_VERBOSE=true export SSHCTL_TIMEOUT=30 ``` --- ## Development ### Setup ```bash 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 ```bash pytest ``` ### Lint & Type Check ```bash 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](LICENSE)