Metadata-Version: 2.4
Name: sshkit
Version: 0.1.8
Summary: 安全的 SSH 客户端工具
Author: yanguangshaonian
License-Expression: MIT
Project-URL: Homepage, https://github.com/yanguangshaonian/sshkit
Project-URL: Repository, https://github.com/yanguangshaonian/sshkit
Project-URL: Issues, https://github.com/yanguangshaonian/sshkit/issues
Keywords: ssh,paramiko,remote-command
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: System :: Networking
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: paramiko>=3.4
Dynamic: license-file

# sshkit

一个基于 Paramiko 的 SSH 客户端工具,提供密码或私钥认证,远程命令执行,统一异常和循环处理能力.

## 安装

```bash
python -m pip install sshkit
```

## 快速使用

连接时自动接受服务器主机密钥,不校验服务器身份,不读取或写入 `known_hosts` 文件.用户认证仍使用密码或私钥,加密私钥可提供私钥口令.

```python
from sshkit import SshClient, SshError

client = SshClient(
	client_name="example-client",
	ip="192.0.2.10",
	port=22,
	username="deploy",
	key_path="/home/deploy/.ssh/id_ed25519",
)

try:
	client.connect()
	result = client.run_once("uname -a", timeout_seconds=5.0)
	print(result.exit_status)
	print(result.stdout_text)
finally:
	client.close()
```

也可以使用上下文管理器:

```python
from sshkit import SshClient

with SshClient(
	client_name="example-client",
	ip="192.0.2.10",
	port=22,
	username="deploy",
	password="password",
) as client:
	result = client.run_once("hostname", timeout_seconds=5.0)
```

`password` 和 `key_path` 必须二选一.加密私钥可以通过 `key_passphrase` 传入 passphrase.程序不会自动使用 `ssh-agent` 或本地默认私钥.

## 错误处理

连接和命令错误统一使用 `SshError`,通过 `SshError.kind` 区分错误类型:

```python
from sshkit import SshError, SshErrorKind

try:
	client.connect()
except SshError as error:
	if error.kind == SshErrorKind.AUTHENTICATION:
		print("认证失败")
	elif error.kind == SshErrorKind.KEY_LOAD:
		print("私钥加载失败")
```

`SshError` 必须传入 `client_name`、`ip`、`port`、`kind` 和 `message`,不再兼容旧的两参数构造.`str(error.kind)` 返回中文错误类型,`error.kind.value` 保留英文分类值.`message` 只传具体详情,异常构造时统一添加类型标题,并写入身份前缀.`str(error)` 返回完整错误消息,例如 `[example-client 192.0.2.10:22] SSH 认证失败: denied`;详情为空时只返回 `[example-client 192.0.2.10:22] SSH 认证失败`.`error.client_name`、`error.ip`、`error.port` 保存身份,`error.cause` 保留原始异常.

## 开发

```bash
python -m pip install -e .
python -m pytest
python -m build
python -m twine check dist/*
```

## 发布到 PyPI

项目提供了交互式发布脚本.它会读取项目元数据并提示发布目标,然后检查工具,清理旧产物,运行测试,构建 wheel/sdist,执行 `twine check`,最后上传到选定的仓库.

不要把 PyPI token 写入代码,配置文件或 git.需要认证时,脚本会交给 `twine` 处理.

直接运行并交互选择目标:

```bash
./publish.sh
```

也可以直接指定目标:

```bash
./publish.sh testpypi
./publish.sh pypi
```

只构建和校验,不上传:

```bash
./publish.sh testpypi --dry-run
```

跳过测试或保留构建产物:

```bash
./publish.sh testpypi --skip-tests
./publish.sh testpypi --keep-artifacts
```

发布流程默认会在退出时清理 `build`、`dist`、`*.egg-info`、`__pycache__`、`.pytest_cache` 和 `*.pyc`. 只清理这些临时文件而不发布:

```bash
./publish.sh --clean-only
```

`--keep-build` 仍可作为 `--keep-artifacts` 的兼容别名. 使用 `--keep-artifacts` 时, 发布产物和构建缓存会保留, 便于排查问题.

建议先上传 TestPyPI,再验证安装流程:

```bash
python -m pip install --index-url https://test.pypi.org/simple/ \
	--no-deps sshkit==0.1.0
```

上面的 TestPyPI 安装命令只安装测试仓库中的 `sshkit`.运行库代码前,请确保依赖已经从正式 PyPI 安装:

```bash
python -m pip install "paramiko>=3.4"
```

确认 TestPyPI 安装正常后,再执行:

```bash
./publish.sh pypi
```

## 许可证

MIT License,详见 [LICENSE](LICENSE).
