improve linux-ssh-operator-skill: bug fixes, rsync support, tunneling docs
- Fix dry-run bug in ssh_alias_setup.sh (pub key check skipped when key was never generated) - Remove dead code in ssh_run.sh (-- check after host arg was never reachable) - Add --rsync/--delete flags to ssh_copy.sh for incremental rsync transfers - Add -C/--compress flag to ssh_run.sh for slow-link connections - Expand SKILL.md/SKILL.zh-CN.md: tunneling, jump host, SSH multiplexing, Docker ops - Expand ssh-playbook.md: port forwarding, bastion, ControlMaster, structured troubleshooting table Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
---
|
||||
name: linux-ssh-operator-skill
|
||||
description: 通过 SSH 连接并操作 Linux 服务器:执行远程命令、查看日志、管理 systemd 服务、传输文件(scp/rsync)、配置 SSH 别名、安装公钥启用免密登录、排障。用户提到 ssh/scp/rsync、远程服务器 IP:端口、配置 ssh 别名、ssh-copy-id、免密登录、systemctl/journalctl、部署到服务器、在服务器上运行命令、远程拷贝文件 等场景时使用。
|
||||
description: 通过 SSH 连接并操作 Linux 服务器:执行远程命令、查看日志、管理 systemd 服务、传输文件(scp/rsync)、配置 SSH 别名、安装公钥启用免密登录、SSH 隧道/端口转发、跳板机、内网穿透、排障。用户提到 ssh/scp/rsync、远程服务器 IP:端口、配置 ssh 别名、ssh-copy-id、免密登录、systemctl/journalctl、部署到服务器、在服务器上运行命令、远程拷贝文件、SSH 隧道、端口转发、跳板机、堡垒机、服务器上看一下、帮我连上服务器、查看服务器日志 等场景时使用。
|
||||
|
||||
---
|
||||
|
||||
# Linux SSH Operator
|
||||
|
||||
## Overview
|
||||
|
||||
Use SSH to connect to Linux servers and perform safe, repeatable remote operations (commands, logs, services, file transfer, alias bootstrap, passwordless login).
|
||||
Use SSH to connect to Linux servers and perform safe, repeatable remote operations (commands, logs, services, file transfer, alias bootstrap, passwordless login, tunnels).
|
||||
|
||||
## Workflow
|
||||
|
||||
@@ -99,29 +100,85 @@ ssh -tt my-server sudo systemctl status nginx --no-pager
|
||||
```bash
|
||||
~/.claude/skills/linux-ssh-operator-skill/scripts/ssh_run.sh my-server -- uname -a
|
||||
~/.claude/skills/linux-ssh-operator-skill/scripts/ssh_run.sh --tty --sudo my-server -- systemctl restart nginx
|
||||
~/.claude/skills/linux-ssh-operator-skill/scripts/ssh_run.sh --compress my-server -- cat /var/log/big.log
|
||||
```
|
||||
|
||||
### Transfer files
|
||||
|
||||
Upload:
|
||||
Upload (scp):
|
||||
|
||||
```bash
|
||||
~/.claude/skills/linux-ssh-operator-skill/scripts/ssh_copy.sh push my-server ./local.txt /tmp/local.txt
|
||||
```
|
||||
|
||||
Download:
|
||||
Download (scp):
|
||||
|
||||
```bash
|
||||
~/.claude/skills/linux-ssh-operator-skill/scripts/ssh_copy.sh pull my-server /var/log/syslog ./syslog
|
||||
```
|
||||
|
||||
Incremental sync (rsync — preferred for directories or large files):
|
||||
|
||||
```bash
|
||||
~/.claude/skills/linux-ssh-operator-skill/scripts/ssh_copy.sh --rsync push my-server ./dist/ /var/www/html/
|
||||
~/.claude/skills/linux-ssh-operator-skill/scripts/ssh_copy.sh --rsync --delete push my-server ./dist/ /var/www/html/
|
||||
~/.claude/skills/linux-ssh-operator-skill/scripts/ssh_copy.sh --rsync pull my-server /var/log/ ./logs/
|
||||
```
|
||||
|
||||
### SSH Tunneling / Port Forwarding
|
||||
|
||||
Local forward (access remote service on a local port):
|
||||
|
||||
```bash
|
||||
# Access remote MySQL (3306) at localhost:13306
|
||||
ssh -L 13306:127.0.0.1:3306 my-server -N
|
||||
```
|
||||
|
||||
Remote forward (expose a local port on the server):
|
||||
|
||||
```bash
|
||||
# Make local :8080 accessible on server's :18080
|
||||
ssh -R 18080:127.0.0.1:8080 my-server -N
|
||||
```
|
||||
|
||||
Jump host (bastion → target):
|
||||
|
||||
```bash
|
||||
ssh -J bastion-user@bastion:22 target-user@target-host
|
||||
```
|
||||
|
||||
Or configure in `~/.ssh/config`:
|
||||
|
||||
```sshconfig
|
||||
Host target-internal
|
||||
HostName 10.0.0.50
|
||||
User ubuntu
|
||||
ProxyJump bastion
|
||||
```
|
||||
|
||||
### SSH Multiplexing (faster repeated connections)
|
||||
|
||||
Add to `~/.ssh/config` to reuse the same TCP connection:
|
||||
|
||||
```sshconfig
|
||||
Host *
|
||||
ControlMaster auto
|
||||
ControlPath ~/.ssh/ctrl-%h-%p-%r
|
||||
ControlPersist 60s
|
||||
```
|
||||
|
||||
The first `ssh my-server` opens a master connection; subsequent connections reuse it without re-handshaking.
|
||||
|
||||
## Common Ops (snippets)
|
||||
|
||||
- Disk: `df -h`, `du -sh /path/* | sort -h`
|
||||
- Memory/CPU: `free -h`, `top`, `ps aux --sort=-%mem | head`
|
||||
- Logs: `journalctl -u SERVICE -n 200 --no-pager`, `tail -n 200 -f /path/log`
|
||||
- Services: `systemctl status|restart|stop SERVICE`
|
||||
- Services: `systemctl status|restart|stop|enable SERVICE`
|
||||
- Networking: `ss -lntp`, `ip a`, `ip r`, `curl -v http://127.0.0.1:PORT/`
|
||||
- Docker: `docker ps`, `docker logs -f CONTAINER`, `docker exec -it CONTAINER bash`
|
||||
- Config test: `nginx -t`, `sshd -T`
|
||||
- Open files: `lsof -i :PORT`, `fuser PORT/tcp`
|
||||
|
||||
## Safety
|
||||
|
||||
@@ -136,5 +193,5 @@ Download:
|
||||
## Scripts
|
||||
|
||||
- `scripts/ssh_alias_setup.sh`: create/update SSH aliases and optionally run `ssh-copy-id`.
|
||||
- `scripts/ssh_run.sh`: run remote commands with consistent options.
|
||||
- `scripts/ssh_copy.sh`: push/pull files via scp with consistent options.
|
||||
- `scripts/ssh_run.sh`: run remote commands with consistent options (supports `--compress`, `--tty`, `--sudo`).
|
||||
- `scripts/ssh_copy.sh`: push/pull files via scp or rsync (use `--rsync` for incremental transfers).
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: linux-ssh-operator-skill
|
||||
description: 通过 SSH 连接并操作 Linux 服务器:执行远程命令、查看日志、管理 systemd 服务、传输文件(scp/rsync)、配置 SSH 别名、安装公钥启用免密登录、排障。用户提到 ssh/scp/rsync、远程服务器 IP:端口、配置 ssh 别名、ssh-copy-id、免密登录、systemctl/journalctl、部署到服务器、在服务器上运行命令、远程拷贝文件 等场景时使用。
|
||||
description: 通过 SSH 连接并操作 Linux 服务器:执行远程命令、查看日志、管理 systemd 服务、传输文件(scp/rsync)、配置 SSH 别名、安装公钥启用免密登录、SSH 隧道/端口转发、跳板机、内网穿透、排障。用户提到 ssh/scp/rsync、远程服务器 IP:端口、配置 ssh 别名、ssh-copy-id、免密登录、systemctl/journalctl、部署到服务器、在服务器上运行命令、远程拷贝文件、SSH 隧道、端口转发、跳板机、堡垒机、服务器上看一下、帮我连上服务器、查看服务器日志 等场景时使用。
|
||||
|
||||
---
|
||||
|
||||
# Linux SSH 运维助手(中文说明)
|
||||
@@ -14,9 +15,10 @@ description: 通过 SSH 连接并操作 Linux 服务器:执行远程命令、
|
||||
- 执行远程命令
|
||||
- 查看系统与服务日志
|
||||
- 管理 `systemd` 服务
|
||||
- 上传和下载文件
|
||||
- 上传和下载文件(scp 或 rsync)
|
||||
- 配置 SSH 别名
|
||||
- 安装公钥并启用免密登录
|
||||
- SSH 隧道 / 端口转发 / 跳板机
|
||||
- 进行常见运维排障
|
||||
|
||||
适合以下场景:
|
||||
@@ -25,9 +27,11 @@ description: 通过 SSH 连接并操作 Linux 服务器:执行远程命令、
|
||||
- 在服务器上运行命令
|
||||
- 查看 `journalctl` / 应用日志
|
||||
- 启动、停止、重启服务
|
||||
- 把文件部署到服务器
|
||||
- 把文件部署到服务器(scp 或 rsync 增量同步)
|
||||
- 从服务器拉取日志、配置或产物文件
|
||||
- 按“别名 + IP + 密码”快速配置一键 SSH 免密登录
|
||||
- 按"别名 + IP + 密码"快速配置一键 SSH 免密登录
|
||||
- 配置端口转发访问内网服务
|
||||
- 通过跳板机/堡垒机连接内网服务器
|
||||
|
||||
## 工作流程
|
||||
|
||||
@@ -71,7 +75,7 @@ ssh my-server
|
||||
|
||||
### 一键配置别名 + 免密登录
|
||||
|
||||
当用户提供“别名、主机/IP、密码”,并希望直接通过 `ssh 别名` 登录时,优先使用下面的流程:
|
||||
当用户提供"别名、主机/IP、密码",并希望直接通过 `ssh 别名` 登录时,优先使用下面的流程:
|
||||
|
||||
1. 使用辅助脚本写入或更新 SSH 别名:
|
||||
|
||||
@@ -118,22 +122,78 @@ ssh -tt my-server sudo systemctl status nginx --no-pager
|
||||
```bash
|
||||
~/.claude/skills/linux-ssh-operator-skill/scripts/ssh_run.sh my-server -- uname -a
|
||||
~/.claude/skills/linux-ssh-operator-skill/scripts/ssh_run.sh --tty --sudo my-server -- systemctl restart nginx
|
||||
~/.claude/skills/linux-ssh-operator-skill/scripts/ssh_run.sh --compress my-server -- cat /var/log/big.log
|
||||
```
|
||||
|
||||
### 传输文件
|
||||
|
||||
上传文件:
|
||||
上传文件(scp):
|
||||
|
||||
```bash
|
||||
~/.claude/skills/linux-ssh-operator-skill/scripts/ssh_copy.sh push my-server ./local.txt /tmp/local.txt
|
||||
```
|
||||
|
||||
下载文件:
|
||||
下载文件(scp):
|
||||
|
||||
```bash
|
||||
~/.claude/skills/linux-ssh-operator-skill/scripts/ssh_copy.sh pull my-server /var/log/syslog ./syslog
|
||||
```
|
||||
|
||||
增量同步(rsync,推荐用于目录或大文件):
|
||||
|
||||
```bash
|
||||
# 上传目录
|
||||
~/.claude/skills/linux-ssh-operator-skill/scripts/ssh_copy.sh --rsync push my-server ./dist/ /var/www/html/
|
||||
# 上传并删除远端多余文件
|
||||
~/.claude/skills/linux-ssh-operator-skill/scripts/ssh_copy.sh --rsync --delete push my-server ./dist/ /var/www/html/
|
||||
# 下载目录
|
||||
~/.claude/skills/linux-ssh-operator-skill/scripts/ssh_copy.sh --rsync pull my-server /var/log/ ./logs/
|
||||
```
|
||||
|
||||
### SSH 隧道 / 端口转发
|
||||
|
||||
本地转发(将远程服务映射到本地端口):
|
||||
|
||||
```bash
|
||||
# 把远程 MySQL(3306) 映射到本地 13306
|
||||
ssh -L 13306:127.0.0.1:3306 my-server -N
|
||||
```
|
||||
|
||||
远程转发(将本地服务暴露到服务器端口):
|
||||
|
||||
```bash
|
||||
# 把本地 :8080 映射到服务器 :18080
|
||||
ssh -R 18080:127.0.0.1:8080 my-server -N
|
||||
```
|
||||
|
||||
跳板机 / 堡垒机:
|
||||
|
||||
```bash
|
||||
ssh -J bastion-user@bastion:22 target-user@target-host
|
||||
```
|
||||
|
||||
或在 `~/.ssh/config` 中配置:
|
||||
|
||||
```sshconfig
|
||||
Host target-internal
|
||||
HostName 10.0.0.50
|
||||
User ubuntu
|
||||
ProxyJump bastion
|
||||
```
|
||||
|
||||
### SSH 多路复用(加速重复连接)
|
||||
|
||||
在 `~/.ssh/config` 中添加,复用同一 TCP 连接:
|
||||
|
||||
```sshconfig
|
||||
Host *
|
||||
ControlMaster auto
|
||||
ControlPath ~/.ssh/ctrl-%h-%p-%r
|
||||
ControlPersist 60s
|
||||
```
|
||||
|
||||
第一次 `ssh my-server` 建立主连接,后续连接直接复用,无需重新握手,速度更快。
|
||||
|
||||
## 常见运维操作
|
||||
|
||||
- 磁盘空间:`df -h`
|
||||
@@ -142,8 +202,11 @@ ssh -tt my-server sudo systemctl status nginx --no-pager
|
||||
- 进程查看:`ps aux --sort=-%mem | head`
|
||||
- 日志查看:`journalctl -u SERVICE -n 200 --no-pager`
|
||||
- 日志跟踪:`tail -n 200 -f /path/log`
|
||||
- 服务管理:`systemctl status|restart|stop SERVICE`
|
||||
- 服务管理:`systemctl status|restart|stop|enable SERVICE`
|
||||
- 网络排查:`ss -lntp`、`ip a`、`ip r`、`curl -v http://127.0.0.1:PORT/`
|
||||
- Docker:`docker ps`、`docker logs -f CONTAINER`、`docker exec -it CONTAINER bash`
|
||||
- 配置测试:`nginx -t`、`sshd -T`
|
||||
- 端口占用:`lsof -i :PORT`、`fuser PORT/tcp`
|
||||
|
||||
## 安全注意事项
|
||||
|
||||
@@ -170,7 +233,8 @@ ssh -tt my-server sudo systemctl status nginx --no-pager
|
||||
|
||||
- 指定用户、端口、私钥
|
||||
- 设置连接超时
|
||||
- 启用 TTY
|
||||
- 启用 TTY(`--tty`)
|
||||
- 启用 SSH 压缩(`--compress`,适合慢速链路)
|
||||
- 使用 `sudo` 或 `sudo -n`
|
||||
- 使用 `--accept-new` 处理首次连接主机
|
||||
- 使用 `--dry-run` 预览实际将执行的命令
|
||||
@@ -184,11 +248,13 @@ ssh -tt my-server sudo systemctl status nginx --no-pager
|
||||
|
||||
### `scripts/ssh_copy.sh`
|
||||
|
||||
用于通过 `scp` 上传或下载文件,支持:
|
||||
用于通过 `scp` 或 `rsync` 上传或下载文件,支持:
|
||||
|
||||
- `push` 上传文件到远程服务器
|
||||
- `pull` 从远程服务器下载文件
|
||||
- 递归复制目录
|
||||
- `--rsync`:使用 rsync 增量同步(显示进度,适合目录和大文件)
|
||||
- `--delete`:配合 `--rsync push` 删除远端多余文件
|
||||
- 递归复制目录(scp 模式用 `-r`)
|
||||
- 指定用户、端口、私钥
|
||||
- 使用 `--accept-new`
|
||||
- 使用 `--dry-run` 预览命令
|
||||
|
||||
@@ -60,6 +60,7 @@ ssh my-server "du -sh /var/log/* | sort -h | tail"
|
||||
```bash
|
||||
ssh my-server "ps aux --sort=-%mem | head"
|
||||
ssh my-server "ss -lntp"
|
||||
ssh my-server "lsof -i :8080"
|
||||
```
|
||||
|
||||
### Logs (systemd)
|
||||
@@ -67,6 +68,7 @@ ssh my-server "ss -lntp"
|
||||
```bash
|
||||
ssh my-server "journalctl -u SERVICE -n 200 --no-pager"
|
||||
ssh my-server "journalctl -u SERVICE -f --no-pager"
|
||||
ssh my-server "journalctl -u SERVICE --since '1 hour ago' --no-pager"
|
||||
```
|
||||
|
||||
### Services (systemd)
|
||||
@@ -89,15 +91,137 @@ Non-interactive sudo (fails if a password prompt would be required):
|
||||
ssh my-server "sudo -n systemctl restart SERVICE"
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
ssh my-server "docker ps"
|
||||
ssh my-server "docker logs --tail=200 -f CONTAINER"
|
||||
ssh my-server "docker exec -it CONTAINER bash"
|
||||
ssh my-server "docker stats --no-stream"
|
||||
```
|
||||
|
||||
### File editing shortcuts
|
||||
|
||||
```bash
|
||||
# View a config file
|
||||
ssh my-server "cat /etc/nginx/nginx.conf"
|
||||
|
||||
# Quick in-place line replacement
|
||||
ssh my-server "sed -i 's/old/new/g' /path/to/file"
|
||||
|
||||
# Append a line
|
||||
ssh my-server "echo 'new line' >> /path/to/file"
|
||||
```
|
||||
|
||||
## SSH Tunneling / Port Forwarding
|
||||
|
||||
### Local forward
|
||||
|
||||
Access a remote service on a local port (server-side port does not need to be publicly open):
|
||||
|
||||
```bash
|
||||
# remote MySQL → localhost:13306
|
||||
ssh -L 13306:127.0.0.1:3306 my-server -N
|
||||
|
||||
# remote Redis → localhost:16379
|
||||
ssh -L 16379:127.0.0.1:6379 my-server -N
|
||||
```
|
||||
|
||||
Add `-f` to background the tunnel:
|
||||
|
||||
```bash
|
||||
ssh -fNL 13306:127.0.0.1:3306 my-server
|
||||
```
|
||||
|
||||
### Remote forward
|
||||
|
||||
Expose a local service on the server (useful for demos or webhooks):
|
||||
|
||||
```bash
|
||||
# local :8080 → server :18080
|
||||
ssh -R 18080:127.0.0.1:8080 my-server -N
|
||||
```
|
||||
|
||||
### Jump host / bastion
|
||||
|
||||
```bash
|
||||
# Single jump
|
||||
ssh -J bastion user@target
|
||||
|
||||
# Multi-hop
|
||||
ssh -J jump1,jump2 user@target
|
||||
```
|
||||
|
||||
`~/.ssh/config` version:
|
||||
|
||||
```sshconfig
|
||||
Host target-internal
|
||||
HostName 10.0.0.50
|
||||
User ubuntu
|
||||
ProxyJump bastion
|
||||
```
|
||||
|
||||
## SSH Multiplexing
|
||||
|
||||
Reuse the same TCP connection for multiple SSH sessions — avoids repeated handshakes:
|
||||
|
||||
```sshconfig
|
||||
Host *
|
||||
ControlMaster auto
|
||||
ControlPath ~/.ssh/ctrl-%h-%p-%r
|
||||
ControlPersist 60s
|
||||
```
|
||||
|
||||
Check active master sockets:
|
||||
|
||||
```bash
|
||||
ls ~/.ssh/ctrl-*
|
||||
```
|
||||
|
||||
Close a master socket manually:
|
||||
|
||||
```bash
|
||||
ssh -O exit my-server
|
||||
```
|
||||
|
||||
## Safer host key handling
|
||||
|
||||
- Prefer verifying the host key fingerprint out-of-band on first connect.
|
||||
- If you must automate first-connect for ephemeral hosts, use `StrictHostKeyChecking=accept-new` (OpenSSH 7.6+).
|
||||
- If you see a "host key changed" warning, treat it as a potential security incident until you confirm the change is expected.
|
||||
|
||||
Remove a stale known_hosts entry:
|
||||
|
||||
```bash
|
||||
ssh-keygen -R SERVER_IP
|
||||
```
|
||||
|
||||
## Troubleshooting quick hits
|
||||
|
||||
- `Permission denied (publickey)`: wrong user, wrong key, server missing your public key, or `sshd` settings.
|
||||
- `Connection timed out`: routing/firewall/security group, wrong port, server down.
|
||||
- `No route to host`: network path missing (VPN, subnet, ACL).
|
||||
| Symptom | Likely cause | Fix |
|
||||
|---|---|---|
|
||||
| `Permission denied (publickey)` | Wrong user/key, pub key not on server, sshd config | Check `~/.ssh/authorized_keys` on server; verify key matches |
|
||||
| `Connection timed out` | Firewall/security group, wrong port, server down | Check port, security group, ping |
|
||||
| `No route to host` | Network path missing (VPN, ACL, subnet) | Check VPN, routing table |
|
||||
| `Host key changed` warning | Server rebuilt, or MITM | Verify with out-of-band channel; `ssh-keygen -R HOST` if expected |
|
||||
| `Too many authentication failures` | SSH agent offering too many keys | Use `-o IdentitiesOnly=yes -i ~/.ssh/id_ed25519` |
|
||||
| `sudo: a terminal is required` | No TTY allocated | Use `ssh -tt` or pass `-t` to wrapper |
|
||||
|
||||
### Debug connection verbosely
|
||||
|
||||
```bash
|
||||
ssh -vvv my-server
|
||||
```
|
||||
|
||||
### Check sshd config on server
|
||||
|
||||
```bash
|
||||
ssh my-server "sudo sshd -T | grep -E 'pubkeyauthentication|passwordauthentication|permitrootlogin'"
|
||||
```
|
||||
|
||||
### Check authorized_keys permissions (common cause of publickey failure)
|
||||
|
||||
```bash
|
||||
ssh my-server "ls -la ~/.ssh/ && cat ~/.ssh/authorized_keys"
|
||||
# .ssh should be 700, authorized_keys should be 600
|
||||
```
|
||||
|
||||
@@ -118,7 +118,7 @@ if [[ ! -f "$key" ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ! -f "$key_pub" ]]; then
|
||||
if ! $dry_run && [[ ! -f "$key_pub" ]]; then
|
||||
echo "Public key not found: $key_pub" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -3,7 +3,7 @@ set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Copy files via scp with consistent options.
|
||||
Copy files via scp or rsync with consistent options.
|
||||
|
||||
Usage:
|
||||
ssh_copy.sh [options] push HOST LOCAL_PATH REMOTE_PATH
|
||||
@@ -13,9 +13,11 @@ Options:
|
||||
-u, --user USER Override SSH user (or set REMOTE_USER)
|
||||
-p, --port PORT SSH port (default: REMOTE_PORT or 22)
|
||||
-i, --key PATH Identity file (default: REMOTE_KEY)
|
||||
-r, --recursive Copy directories recursively
|
||||
-r, --recursive Copy directories recursively (scp mode only; rsync always recurses)
|
||||
--rsync Use rsync instead of scp (incremental, shows progress)
|
||||
--delete With --rsync push: delete remote files not present in source
|
||||
--accept-new Set StrictHostKeyChecking=accept-new
|
||||
--dry-run Print the scp command that would run
|
||||
--dry-run Print the command that would run
|
||||
-h, --help Show help
|
||||
|
||||
Environment defaults:
|
||||
@@ -23,6 +25,8 @@ Environment defaults:
|
||||
|
||||
Examples:
|
||||
ssh_copy.sh push my-server ./app.tar.gz /tmp/app.tar.gz
|
||||
ssh_copy.sh --rsync push my-server ./dist/ /var/www/html/
|
||||
ssh_copy.sh --rsync --delete push my-server ./dist/ /var/www/html/
|
||||
ssh_copy.sh --user ubuntu pull 10.0.0.1 /var/log/syslog ./syslog
|
||||
USAGE
|
||||
}
|
||||
@@ -32,47 +36,28 @@ user="${REMOTE_USER:-}"
|
||||
key="${REMOTE_KEY:-}"
|
||||
|
||||
recursive=false
|
||||
use_rsync=false
|
||||
delete_mode=false
|
||||
accept_new=false
|
||||
dry_run=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-u|--user)
|
||||
user="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
-p|--port)
|
||||
port="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
-i|--key)
|
||||
key="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
-r|--recursive)
|
||||
recursive=true
|
||||
shift
|
||||
;;
|
||||
--accept-new)
|
||||
accept_new=true
|
||||
shift
|
||||
;;
|
||||
--dry-run)
|
||||
dry_run=true
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
-u|--user) user="${2:-}"; shift 2 ;;
|
||||
-p|--port) port="${2:-}"; shift 2 ;;
|
||||
-i|--key) key="${2:-}"; shift 2 ;;
|
||||
-r|--recursive) recursive=true; shift ;;
|
||||
--rsync) use_rsync=true; shift ;;
|
||||
--delete) delete_mode=true; shift ;;
|
||||
--accept-new) accept_new=true; shift ;;
|
||||
--dry-run) dry_run=true; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
-*)
|
||||
echo "Unknown option: $1" >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
*)
|
||||
break
|
||||
;;
|
||||
*) break ;;
|
||||
esac
|
||||
done
|
||||
|
||||
@@ -81,11 +66,8 @@ if [[ $# -lt 4 ]]; then
|
||||
exit 2
|
||||
fi
|
||||
|
||||
direction="$1"
|
||||
shift
|
||||
|
||||
host="$1"
|
||||
shift
|
||||
direction="$1"; shift
|
||||
host="$1"; shift
|
||||
|
||||
dest_host="$host"
|
||||
if [[ -n "$user" ]]; then
|
||||
@@ -93,34 +75,70 @@ if [[ -n "$user" ]]; then
|
||||
dest_host="${user}@${host_no_user}"
|
||||
fi
|
||||
|
||||
scp_opts=(-P "$port" -p)
|
||||
if [[ -n "$key" ]]; then
|
||||
scp_opts+=(-i "$key" -o "IdentitiesOnly=yes")
|
||||
fi
|
||||
if $recursive; then
|
||||
scp_opts+=(-r)
|
||||
fi
|
||||
if $accept_new; then
|
||||
scp_opts+=(-o "StrictHostKeyChecking=accept-new")
|
||||
fi
|
||||
if $use_rsync; then
|
||||
if ! command -v rsync >/dev/null 2>&1; then
|
||||
echo "rsync is required but not installed." >&2
|
||||
exit 1
|
||||
fi
|
||||
ssh_parts=("ssh" "-p" "$port")
|
||||
if [[ -n "$key" ]]; then
|
||||
ssh_parts+=("-i" "$key" "-o" "IdentitiesOnly=yes")
|
||||
fi
|
||||
if $accept_new; then
|
||||
ssh_parts+=("-o" "StrictHostKeyChecking=accept-new")
|
||||
fi
|
||||
rsync_opts=(-avz --progress -e "${ssh_parts[*]}")
|
||||
|
||||
case "$direction" in
|
||||
push)
|
||||
local_path="$1"
|
||||
remote_path="$2"
|
||||
full_cmd=(scp "${scp_opts[@]}" "$local_path" "${dest_host}:${remote_path}")
|
||||
;;
|
||||
pull)
|
||||
remote_path="$1"
|
||||
local_path="$2"
|
||||
full_cmd=(scp "${scp_opts[@]}" "${dest_host}:${remote_path}" "$local_path")
|
||||
;;
|
||||
*)
|
||||
echo "Unknown direction: $direction (expected: push|pull)" >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
case "$direction" in
|
||||
push)
|
||||
local_path="$1"
|
||||
remote_path="$2"
|
||||
if $delete_mode; then
|
||||
rsync_opts+=(--delete)
|
||||
fi
|
||||
full_cmd=(rsync "${rsync_opts[@]}" "$local_path" "${dest_host}:${remote_path}")
|
||||
;;
|
||||
pull)
|
||||
remote_path="$1"
|
||||
local_path="$2"
|
||||
full_cmd=(rsync "${rsync_opts[@]}" "${dest_host}:${remote_path}" "$local_path")
|
||||
;;
|
||||
*)
|
||||
echo "Unknown direction: $direction (expected: push|pull)" >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
else
|
||||
scp_opts=(-P "$port" -p)
|
||||
if [[ -n "$key" ]]; then
|
||||
scp_opts+=(-i "$key" -o "IdentitiesOnly=yes")
|
||||
fi
|
||||
if $recursive; then
|
||||
scp_opts+=(-r)
|
||||
fi
|
||||
if $accept_new; then
|
||||
scp_opts+=(-o "StrictHostKeyChecking=accept-new")
|
||||
fi
|
||||
|
||||
case "$direction" in
|
||||
push)
|
||||
local_path="$1"
|
||||
remote_path="$2"
|
||||
full_cmd=(scp "${scp_opts[@]}" "$local_path" "${dest_host}:${remote_path}")
|
||||
;;
|
||||
pull)
|
||||
remote_path="$1"
|
||||
local_path="$2"
|
||||
full_cmd=(scp "${scp_opts[@]}" "${dest_host}:${remote_path}" "$local_path")
|
||||
;;
|
||||
*)
|
||||
echo "Unknown direction: $direction (expected: push|pull)" >&2
|
||||
usage >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if $dry_run; then
|
||||
printf '%q ' "${full_cmd[@]}"
|
||||
|
||||
@@ -14,6 +14,7 @@ Options:
|
||||
-p, --port PORT SSH port (default: REMOTE_PORT or 22)
|
||||
-i, --key PATH Identity file (default: REMOTE_KEY)
|
||||
-t, --tty Force pseudo-tty allocation (useful for sudo prompts)
|
||||
-C, --compress Enable SSH compression (useful on slow links)
|
||||
--accept-new Set StrictHostKeyChecking=accept-new
|
||||
--sudo Prefix command with sudo --
|
||||
--sudo-non-interactive Prefix command with sudo -n -- (fails if password needed)
|
||||
@@ -36,6 +37,7 @@ key="${REMOTE_KEY:-}"
|
||||
connect_timeout="${REMOTE_CONNECT_TIMEOUT:-10}"
|
||||
|
||||
tty=false
|
||||
compress=false
|
||||
accept_new=false
|
||||
sudo_mode=""
|
||||
dry_run=false
|
||||
@@ -58,6 +60,10 @@ while [[ $# -gt 0 ]]; do
|
||||
tty=true
|
||||
shift
|
||||
;;
|
||||
-C|--compress)
|
||||
compress=true
|
||||
shift
|
||||
;;
|
||||
--accept-new)
|
||||
accept_new=true
|
||||
shift
|
||||
@@ -126,14 +132,15 @@ if $accept_new; then
|
||||
ssh_opts+=(-o "StrictHostKeyChecking=accept-new")
|
||||
fi
|
||||
|
||||
if $compress; then
|
||||
ssh_opts+=(-C)
|
||||
fi
|
||||
|
||||
if $tty; then
|
||||
ssh_opts+=(-tt)
|
||||
fi
|
||||
|
||||
cmd=("$@")
|
||||
if [[ ${#cmd[@]} -gt 0 && "${cmd[0]}" == "--" ]]; then
|
||||
cmd=("${cmd[@]:1}")
|
||||
fi
|
||||
if [[ -n "$sudo_mode" && ${#cmd[@]} -gt 0 ]]; then
|
||||
if [[ "$sudo_mode" == "sudo-n" ]]; then
|
||||
cmd=("sudo" "-n" "--" "${cmd[@]}")
|
||||
|
||||
Reference in New Issue
Block a user