Metadata-Version: 2.5
Name: remote-docker-cli
Version: 0.2.0
Summary: A CLI tool for managing remote Docker hosts
Project-URL: Homepage, https://gitee.com/kzhuo/remote-docker
Project-URL: Issues, https://gitee.com/kzhuo/remote-docker/issues
Project-URL: Repository, https://gitee.com/kzhuo/remote-docker
Author-email: ZHUO kaikuo <kaikuo.zhuo@hotmail.com>
License-Expression: MIT
Keywords: cli,devops,docker,remote,ssh
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development
Classifier: Topic :: System :: Systems Administration
Requires-Python: >=3.10
Requires-Dist: click
Requires-Dist: pyyaml
Provides-Extra: all
Requires-Dist: paramiko>=3.0; extra == 'all'
Provides-Extra: ssh
Requires-Dist: paramiko>=3.0; extra == 'ssh'
Description-Content-Type: text/markdown

# rdocker (remote-docker)

一个类 Docker 命令行工具，通过 SSH 将 `pull` / `save` / `load` 操作代理到远程主机上的 `skopeo`。
解析 `docker-compose.yml`，在远端拉取所有镜像，打包成 `.tar.gz` 下载到本地，再 `docker load` 到本地 Docker 引擎。

适用场景：内网/隔离网络中，本地无法直接拉取镜像，借助能访问镜像仓库的远程"跳板机"中转。

---

## 架构

```
本地机器                                      远程机器 (SSH)
┌────────────────────┐                       ┌──────────────────────────────┐
│ docker-compose.yml │                       │  /tmp/rdocker/<name>/        │
└────────┬───────────┘                       │   ├── nginx_latest.tar       │
         │ 解析 + 自动补 :latest              │   ├── redis_7-alpine.tar     │
         ▼                                   │   └── ...                    │
   镜像列表 ──ssh+skopeo───────────────▶    │  skopeo copy docker://...    │
                                            │  docker-archive:xx.tar       │
                                            └──────────────┬───────────────┘
                                                           │ tar -czf
                                                           ▼
                                            /tmp/<name>.tar.gz
                                                           │ scp
                                                           ▼
                                            ./<name>.tar.gz  ← 本地产物
                                                           │ docker load
                                                           ▼
                                            本地 Docker 引擎
```

`skopeo copy` 会带上 `--override-arch` 选项，自动按本地机器架构 (`amd64` / `arm64` / ...) 拉取对应镜像清单。

---

## 安装

需要 Python ≥ 3.10。

### 使用 pip

```bash
pip install remote-docker

# 可选：启用 paramiko 后端（当前版本使用系统 ssh，可不装）
pip install "remote-docker[ssh]"
```

### 从源码安装（推荐使用 uv）

```bash
git clone https://github.com/kzhuo/remote-docker.git
cd remote-docker
uv sync
uv pip install -e .
```

安装后 `rdocker` 命令即可使用：

```bash
rdocker --help
```

### 远端机器前置条件

```bash
# 在远端 SSH 主机上：
skopeo --version
tar --version
```

---

## 快速开始

```bash
# 1. 交互式初始化配置（写入 ~/.config/rdocker/config.yml）
rdocker config init

# 2. 先用 --try 预览将要执行的远程命令（推荐）
rdocker all docker-compose.yml --try

# 3. 一键完成：解析 → 远端拉镜像 → 打包 → 下载 → 本地 load
rdocker all docker-compose.yml

# 4. 完成！镜像已在本地 Docker 中
docker images
```

---

## 配置

配置文件位置：**`~/.config/rdocker/config.yml`**（权限：`0600`）

### config 子命令

| 命令 | 说明 |
|------|------|
| `rdocker config init` | 交互式生成配置（提示输入 host/user/port/key/auth） |
| `rdocker config init --force` / `-f` | 强制覆盖已有配置，不询问 |
| `rdocker config show` | 查看当前配置（密码自动脱敏为 `***`） |
| `rdocker config set <key> <value>` | 设置单个配置项 |
| `rdocker config unset <key>` | 删除单个配置项 |
| `rdocker config path` | 打印配置文件路径 |

### 配置项

| 键 | 说明 | 默认值 |
|----|------|--------|
| `host` | 远端 SSH 主机（IP 或域名） | _(必填)_ |
| `user` | SSH 用户名 | 当前系统用户名 (`getpass.getuser()`) |
| `port` | SSH 端口 | `22` |
| `key` | SSH 私钥路径 | _(空，使用 `ssh-agent` / `~/.ssh/id_*` 系统发现)_ |
| `remote_dir` | 远端工作目录 | `/tmp/rdocker` |
| `registry_auth` | 镜像仓库认证（`user:pass`） | _(可选)_ |

> **关于 `key`**：留空时不会向 `ssh` / `scp` 传 `-i` 参数，由 SSH 自行从 `ssh-agent` 和 `~/.ssh/id_rsa`、`id_ed25519` 等常见位置发现密钥。

### 配置优先级

```
CLI 参数 (--host)  >  环境变量 (rdocker_HOST)  >  配置文件  >  内置默认值
```

### 示例

```bash
# 交互式初始化
$ rdocker config init
🔧 rdocker Config Init
──────────────────────────────────────────────────
  Remote host (IP or domain) []: 192.168.1.100
  SSH user [kaikuo]: deploy
  SSH port [22]: 2222
  SSH private key path (empty = system default) []:
  Registry auth (user:pass, leave empty if none) []: admin:secret123
  Remote working directory [/tmp/rdocker]: /data/rdocker
✅ Config saved to /home/user/.config/rdocker/config.yml
🔍 Testing SSH connection ...
  ✅ SSH connection successful
  ✅ skopeo is available on remote

# 查看配置（密码脱敏）
$ rdocker config show
📋 rdocker Configuration
──────────────────────────────────────────────────
  Config file: /home/user/.config/rdocker/config.yml
  Permissions: 600

  host            = 192.168.1.100
  user            = deploy
  port            = 2222
  key             =
  remote_dir      = /data/rdocker
  registry_auth   = admin:***

  Priority: CLI flag > env var (rdocker_*) > config file > default

# 修改单个字段
$ rdocker config set host 10.0.0.50
✅ Config saved to ~/.config/rdocker/config.yml
   host = 10.0.0.50

# 删除字段
$ rdocker config unset registry_auth
✅ Config saved to ~/.config/rdocker/config.yml
   removed: registry_auth

# 通过环境变量临时覆盖
$ rdocker_HOST=override.host rdocker config show
# → host = override.host
```

---

## 命令

所有动作命令均支持 `--try` 干跑：仅打印将要执行的 ssh / scp / skopeo 命令，不实际执行。

`--name` 缺省规则：
- **compose 模式**（`pull` / `all` / `save` / `fetch` / `cleanup`） → 当前目录最后一级目录名
- **image 模式**（`spull`） → 镜像去除 tag 后的最后一段（如 `nginx:1.25-alpine` → `nginx`，`registry:5000/myapp:v1` → `myapp`）

### `rdocker all <compose_file> [--name <bundle>] [--try]`
一键完整流水线：解析 → 远端拉镜像 → 远端打包 → 下载 → 本地 load。

```bash
rdocker all docker-compose.yml                       # --name 缺省 = cwd 短名
rdocker all docker-compose.yml --name prod --registry-auth admin:pass --no-load
rdocker all docker-compose.yml --try                  # 干跑 4 阶段
```

选项：
- `--name, -n` — Bundle 名称（缺省 = cwd 短名）
- `--registry-auth, -a` — 镜像仓库认证 `user:pass`
- `--output-dir, -o` — 本地输出目录（默认：`.`）
- `--no-cleanup` — 不清理远端临时文件
- `--no-load` — 下载后跳过 `docker load`
- `--try` — 干跑模式

### `rdocker pull <compose_file> [--name <bundle>] [--try]`
解析 compose 文件并通过 skopeo 拉取所有镜像到远端主机。

```bash
rdocker pull docker-compose.yml
rdocker pull docker-compose.yml --name myapp
rdocker pull docker-compose.yml --name myapp --try    # 干跑
```

选项：
- `--name, -n` — Bundle 名称（缺省 = cwd 短名）
- `--registry-auth, -a` — 镜像仓库认证 `user:pass`
- `--try` — 干跑模式

### `rdocker spull <image> [--name <bundle>] [--try]`
拉取**单个镜像**到远端主机（不解析 compose 文件）。

```bash
rdocker spull nginx:1.25-alpine                       # --name 缺省 = "nginx"
rdocker spull registry:5000/myapp:v1 --name myapp
rdocker spull nginx:1.25-alpine --try
```

选项：
- `--name, -n` — Bundle 名称（缺省 = 镜像去除 tag 后的最后一段）
- `--registry-auth, -a` — 镜像仓库认证 `user:pass`
- `--try` — 干跑模式

### `rdocker save [--name <bundle>] [--try]`
将远端所有 `.tar` 文件打包成一个 `.tar.gz`。

```bash
rdocker save                                          # --name 缺省 = cwd 短名
rdocker save --name mybundle --try
```

选项：
- `--name, -n` — Bundle 名称（缺省 = cwd 短名）
- `--try` — 干跑模式

### `rdocker fetch [--name <bundle>] [--try]`
把远端 `.tar.gz` bundle 下载到本地。

```bash
rdocker fetch --output-dir ./dist
rdocker fetch --name mybundle --output-dir ./dist --try
```

选项：
- `--name, -n` — Bundle 名称（缺省 = cwd 短名）
- `--output-dir, -o` — 本地输出目录（默认：`.`）
- `--try` — 干跑模式

### `rdocker load [--name <bundle> | --file <tar.gz>] [--try]`
解压本地 `.tar.gz` 并依次 `docker load` 每个镜像；如果本地文件不存在且指定了 `--name`，会自动从远端 fetch。

```bash
rdocker load --name mybundle
rdocker load --file ./mybundle.tar.gz
rdocker load --name mybundle --try                    # 本地无文件时打印会触发的 scp + load
```

选项：
- `--name, -n` — Bundle 名称（与 `--file` 二选一）
- `--file, -f` — 本地 `.tar.gz` 文件路径
- `--try` — 干跑模式

### `rdocker cleanup [--name <bundle>] [--try]`
清理远端临时文件（`/tmp/rdocker/<name>` 与 `/tmp/<name>.tar.gz`）。

```bash
rdocker cleanup
rdocker cleanup --name mybundle --try
```

选项：
- `--name, -n` — Bundle 名称（缺省 = cwd 短名）
- `--try` — 干跑模式

---

## 环境变量

| 变量 | 对应配置 | 说明 |
|------|----------|------|
| `rdocker_HOST` | `host` | 远端 SSH 主机 |
| `rdocker_USER` | `user` | SSH 用户 |
| `rdocker_PORT` | `port` | SSH 端口 |
| `rdocker_KEY` | `key` | SSH 私钥路径 |
| `rdocker_REMOTE_DIR` | `remote_dir` | 远端工作目录 |
| `rdocker_REGISTRY_AUTH` | `registry_auth` | 镜像仓库认证 |

---

## 工作原理

1. **解析** — `docker-compose.yml` → 提取所有 `service.image`，没有 tag 的自动补 `:latest`，正确处理 `registry:port/path:tag` 形式。
2. **远端拉镜像 (pull)** — SSH 到远端主机，对每个镜像执行：
   ```bash
   skopeo copy --override-arch <arch> [--src-creds user:pass] \
     docker://<img> \
     docker-archive:/tmp/rdocker/<name>/<safe>.tar:<img>
   ```
   `--override-arch` 由本地架构（`x86_64`→`amd64`、`aarch64`→`arm64` 等）自动决定，确保远端拉取的镜像清单与本地 docker 引擎匹配。
3. **远端打包 (save)** — `tar -czf /tmp/<name>.tar.gz -C /tmp/rdocker/<name> .`
4. **下载 (fetch)** — `scp user@host:/tmp/<name>.tar.gz ./`
5. **本地 load** — 解压 tar.gz，依次 `docker load -i <each.tar>`。

---

## Tag 自动补全规则

| compose 中的写法 | 实际使用 |
|------------------|----------|
| `nginx:1.25-alpine` | `nginx:1.25-alpine` (保持不变) |
| `busybox` | `busybox:latest` (自动补) |
| `registry:5000/app:v1` | `registry:5000/app:v1` (port 与 tag 正确识别) |
| `harbor:8443/lib/c:7` | `harbor:8443/lib/c:7` (port 与 tag 正确识别) |

---

## 项目结构

```
remote-docker/
├── src/
│   └── remote_docker/
│       ├── __init__.py        # 包元信息 (__version__)
│       ├── __main__.py        # python -m remote_docker 入口
│       └── cli.py             # Click CLI 主体
├── pyproject.toml             # 项目与依赖配置（hatchling 构建）
├── uv.lock                    # uv 锁定文件
└── README.md
```

---

## 开发

```bash
# 安装（含 dev 依赖）
uv sync

# 直接运行
uv run rdocker --help

# 以模块方式运行
uv run python -m remote_docker --help
```

`pyproject.toml` 声明的入口脚本：

```toml
[project.scripts]
rdocker = "remote_docker.cli:main"
```

---

## License

MIT