Metadata-Version: 2.5
Name: remote-docker-cli
Version: 0.3.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: dev
Requires-Dist: pytest>=7.0; extra == 'dev'
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` / ...) 拉取对应镜像清单。

### 可选通道：Kaggle（无需 SSH 跳板机）

```
本地机器                                    Kaggle (Notebook)
┌────────────────────┐                     ┌──────────────────────────────┐
│ docker-compose.yml │                     │  /kaggle/working/<name>/     │
└────────┬───────────┘                     │   ├── nginx_latest.tar       │
         │ 解析 + 自动补 :latest            │   ├── redis_7-alpine.tar     │
         ▼                                 │   └── ...                    │
   镜像列表  ──`kaggle kernels push`───▶  │  skopeo copy --override-arch │
   (在 .kaggle/<name>/ 暂存)               │                              │
                                          │  tar -czf <name>.tar.gz .    │
                                          └──────────────┬───────────────┘
                                                         │ output
                                                         ▼
                                          ./<name>.tar.gz  ← 本地产物
                                                         │ docker load
                                                         ▼
                                          本地 Docker 引擎
```

详见 [Kaggle 通道](#kaggle-通道) 一节。

---

## 安装

需要 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 <tar.gz> [--try]`
解压本地 `.tar.gz` 并依次 `docker load` 每个镜像。`FILE` 是必填的位置参数；不再支持 `--name`/`--file` 选项，也不再自动从远端 fetch —— 需要远端文件时请显式 `rdocker fetch` / `rdocker kaggle fetch` 之后再 `rdocker load`。

```bash
rdocker load ./mybundle.tar.gz
rdocker load ./myapp.tar.gz --try                     # 打印将要执行的 tar 解压 + docker load 命令
```

参数：
- `FILE` — 本地 `.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` — 干跑模式

---

## Kaggle 通道

无 SSH 跳板机时，可把镜像拉取与打包放到 Kaggle 的一次性 notebook 上跑，产出的 `.tar.gz` 走 `kaggle kernels output` 下载到本地，再被 `rdocker load` 直接消费。

### 前置条件

1. Kaggle 账号（任意注册方式都可）。
2. `pip install kaggle`（Python ≥ 3.10）。
3. 配置 Kaggle 凭据，使 `kaggle config view` 能输出非空 `username`：
   - 首次会引导你把 API token 放到 `~/.kaggle/credentials.json`，**或**
   - 设置环境变量 `KAGGLE_USERNAME` / `KAGGLE_KEY`。
4. 验证：`kaggle config view` 应当看到一行 `- username: <你的名字>`。

### 三步工作流

```bash
# 1. 解析 compose 并推送一个 notebook 到 Kaggle
rdocker kaggle upload docker-compose.yml --name myapp

# 2. 等待 kernel 在 Kaggle 网站跑完（通常几分钟）
#    可在 https://www.kaggle.com/<username>/rdocker-myapp 看日志

# 3. 下载产出的 .tar.gz 到本地
rdocker kaggle fetch --name myapp

# 4. 加载到本地 Docker
rdocker load ./myapp.tar.gz
```

### `rdocker kaggle upload <compose_file> [--name <bundle>] [--try]`

解析 `docker-compose.yml`、在 `.kaggle/<bundle>/` 暂存一个 `rdocker.py` 与 `kernel-metadata.json`、调用 `kaggle kernels push` 推上去。`--try` 模式下不写盘也不推送，只在 stdout 打印将要上传的 notebook 完整内容（含 skopeo 安装段、镜像循环、最终 tar 打包），方便审核。

```bash
rdocker kaggle upload docker-compose.yml                       # --name 缺省 = cwd 短名
rdocker kaggle upload docker-compose.yml --name prod --registry-auth admin:pass
rdocker kaggle upload docker-compose.yml --try                 # 只打印不推送
```

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

### `rdocker kaggle fetch [--name <bundle>] [--output-dir <dir>] [--try]`

从 Kaggle 下载 `<bundle>.tar.gz` 到本地。下载后可被 `rdocker load <bundle>.tar.gz` 直接消费。

```bash
rdocker kaggle fetch --name myapp                              # 下载到 cwd
rdocker kaggle fetch --name myapp --output-dir ./dist           # 下载到 ./dist
rdocker kaggle fetch --name myapp --try                        # 只打印命令
```

选项：
- `--name, -n` — Bundle 名称（缺省 = cwd 短名）
- `--output-dir, -o` — 本地输出目录（默认：`.`）
- `--try` — 干跑：只打印 `kaggle kernels output` 命令

### 内部机制

- **username 解析**：`rdocker` 不直接读 `~/.kaggle/credentials.json`，而是 `subprocess.run(["kaggle", "config", "view"])` 并解析 `- username: <name>` 行 —— 自动跟随 Kaggle CLI 自身的配置源优先级（环境变量 > `~/.kaggle/` > `/etc/kaggle.json`）。
- **kernel 命名**：固定为 `<username>/rdocker-<bundle>`，私有（`is_private: true`），便于 `fetch` 端稳定定位。
- **架构**：`skopeo --override-arch <local_arch>` 的 arch 在本地计算后注入 notebook，确保拉到的镜像清单与本地 Docker 引擎匹配。
- **skopeo 兜底**：notebook 第一段是 `which skopeo || apt-get install -y skopeo || pip install skopeo`（Kaggle 基础镜像通常自带，但本工具不依赖此假设）。

### 常见问题

- **kaggle CLI 缺失 / 未配置** → `pip install kaggle` 后 `kaggle config view` 验证。`rdocker` 会把错误指向这两步。
- **kernel 跑失败** → 在 https://www.kaggle.com/<username>/rdocker-<bundle> 看 stderr；常因镜像仓库私有需 `--registry-auth`，或 compose 中含 Kaggle 网络策略拉不到的镜像。
- **arch 不匹配** → `rdocker` 默认按本地 `platform.machine()` 注入 `--override-arch`；如需覆盖，临时改 `KAGGLE_NOTEBOOK_TEMPLATE` 中的 `ARCH` 注入值（进阶用法）。
- **output 还没生成** → `kaggle kernels output` 报错时直接看返回的 stderr；常见原因是 kernel 仍在排队（免费版 CPU 偶尔排队几分钟）或已失败。
- **`.kaggle/` 目录膨胀** → 该目录在 CWD 下，是 `kaggle kernels push` 的暂存区，可随时 `rm -rf .kaggle`。

---

## 环境变量

| 变量 | 对应配置 | 说明 |
|------|----------|------|
| `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