pi-ssh-helper0.2.0
Run pi helper commands and diagnostics on a remote host over SSH, and route ROS/Python/Rust helper tools to it through the pi-helper-core transport seam
Untested on omp
This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.
Reports 0
No reports yet.
Readme
pi-ssh-helper
SSH 너머의 장비(로봇, 워크스테이션, 임베디드 보드)에서 pi의 헬퍼 도구를 그대로 쓰기 위한 확장입니다.
두 가지를 합니다.
- 원격 진단 —
ssh_host_status,ssh_detect_capabilities,ssh_exec로 원격의 도달성·툴체인·명령 결과를 확인합니다. - 도구 라우팅 —
/ssh-remote on <host>한 번으로pi-ros-helper/pi-python-helper/pi-rust-helper의 모든 서브프로세스가 원격에서 실행되게 합니다.
왜 별도 패키지인가
pi-helper-core 0.1.3의 transport seam을 사용합니다. 코어의 모든 명령은 runCommand() 한 곳을 지나고, 여기에 transport를 등록하면 호출부 30여 곳을 고치지 않고도 전체가 원격을 보게 됩니다. 이 확장은 그 transport의 SSH 구현체입니다.
pi-ssh-helper ──setTransport()──► pi-helper-core
│ runCommand()
▼
pi-ros-helper / pi-python-helper / pi-rust-helper (무수정)
로컬 read/write/edit/bash 라우팅, SFTP, 포트포워딩은 구현하지 않습니다. 그 영역에는 이미 상위 품질의 확장이 있고, 재구현은 손실입니다.
설치
pi install npm:pi-ssh-helper
저장소에서 직접 설치하거나 개발 중에 쓸 때:
pi install git:github.com/wkqco33/pi-ssh-helper
pi install ./pi-ssh-helper # 이 저장소를 클론한 디렉터리에서
설정
연결 정보는 ~/.ssh/config를 그대로 재사용하고, ssh에 없는 개념만 ~/.pi/agent/ssh-helper.json에 적습니다.
{
"defaultHost": "robot-01",
"hosts": {
"robot-01": {
"hostname": "10.0.0.5",
"user": "robot",
"port": 22,
"remoteWorkspace": "/home/robot/ws",
"localWorkspace": "/home/me/ws",
"prelude": ["source /opt/ros/humble/setup.bash"],
"allowMutating": false
}
}
}
| 키 | 의미 |
|---|---|
hostname / user / port / identityFile / proxyJump |
생략하면 ~/.ssh/config의 같은 alias 값이 쓰입니다. ssh 규칙대로 먼저 나온 값이 이깁니다 |
remoteWorkspace |
같은 프로젝트의 원격 경로. 필수 — 추측하지 않습니다 |
localWorkspace |
remoteWorkspace에 대응하는 로컬 경로. 생략하면 /ssh-remote on 시점의 pi cwd |
prelude |
매 명령 전에 실행할 셸 라인. ROS setup.bash가 여기 들어갑니다 |
allowMutating |
mutating/irreversible 명령 허용 여부. 기본 false |
connectTimeoutSeconds |
ssh 연결 타임아웃. 기본 10 |
설정 파일의 오타는 거부됩니다. remoteWorksapce처럼 무시되면 나중에 "엉뚱한 디렉터리에서 빌드했다"로 나타나기 때문입니다. PI_SSH_HELPER_CONFIG로 경로를 바꿀 수 있습니다.
Match/Include/ProxyCommand/! 부정 패턴은 해석하지 않습니다. 그런 설정에 의존하는 호스트는 helper 설정에 직접 적어야 합니다.
사용
/ssh-remote on robot-01 # 활성화 (도달성 probe 성공 후에만)
/ssh-remote status # 현재 대상
/ssh-remote hosts # 설정된 alias 목록
/ssh-remote off # 로컬로 복귀
활성화는 사용자 명령으로만 됩니다. 모델이 스스로 모든 헬퍼 도구를 다른 장비로 돌리면 잘못 조준한 것을 알아챌 방법이 없기 때문입니다.
| 도구 | 위험도 | 설명 |
|---|---|---|
ssh_host_status |
read-only | 도달성, 지연, 원격 userland(bash/ros2/python3/rsync/timeout/sftp/docker) 유무 |
ssh_detect_capabilities |
read-only | 커널, bash/python 버전, /opt/ros 배포판 |
ssh_exec |
명령에 따라 다름 | 원격 셸 명령 1회 실행, 출력 상한 적용 |
안전 규칙
- 경로 매핑은 fail-closed입니다. 로컬 cwd가 매핑 루트 밖이면 명령을 실행하지 않고 실패합니다. 엉뚱한 트리에서 빌드/테스트가 도는 것보다 낫습니다.
BatchMode=yes— 비밀번호·호스트키 질문은 도구 호출을 매달리게 하므로 즉시 실패시킵니다. 키 인증이 전제입니다.- 원격 변경은 2단계 opt-in — 호스트 설정의
allowMutating: true그리고 호출의allowMutating: true가 모두 있어야 실행됩니다. - 환경은 전달하지 않습니다 — 호출별 overlay만 원격으로 보냅니다. 로컬
PATH를 보내면 같은 이름의 로컬 바이너리가 원격 툴체인을 가립니다. - 출력 상한 — stdout/stderr는 100KB에서 잘리고, 모델에게는 16KB까지만 전달합니다.
- 타임아웃은 로컬 — 로컬 타이머가 ssh 클라이언트 프로세스 그룹을 종료합니다. 원격에 GNU
timeout이 없어도 동작합니다. 원격 프로세스가 남을 수 있는 것은 알려진 한계입니다.
컨테이너 안에서 실행 (로봇 등)
도구체인이 호스트가 아니라 컨테이너에서 도는 장비(예: CLOi 로봇)는 container와 containerWorkdir를 지정합니다. 그러면 헬퍼 명령과 probe가 docker exec로 컨테이너 안에서 실행됩니다.
{
"hosts": {
"robot-01": {
"hostname": "10.0.0.5",
"user": "robot",
"remoteWorkspace": "/home/robot/ws",
"localWorkspace": "/home/me/ws",
"container": "cloi-robo-claw",
"containerWorkdir": "/ws",
"prelude": ["source /opt/ros/jazzy/setup.bash", "source /ws/install/setup.bash"]
}
}
}
- 로컬 cwd는
containerWorkdir로 매핑됩니다(호스트 체크아웃 경로가 아니라 컨테이너 마운트 지점). 매핑 불가면 fail-closed. prelude는 컨테이너 안에서 실행됩니다.containerWorkdir는 필수입니다. 마운트 지점을 추측하면 엉뚱한 디렉터리에서 도구가 돌기 때문입니다.ssh_exec는 기본이 호스트이고, 컨테이너에서 실행하려면container인자를 넘깁니다. 이때cwd를 생략하면containerWorkdir이 기본값이 됩니다.
// 호스트 진단
{ "host": "robot-01", "command": "docker ps --format '{{.Names}}'" }
// 컨테이너 안 ROS 진단
{ "host": "robot-01", "command": "ros2 node list --no-daemon", "container": "cloi-robo-claw" }
ssh_exec의command는 로컬 셸에서 한 번 평가된 뒤 원격으로 전달됩니다.$(...)를 컨테이너 안에서 확장하려면 작은따옴표로 감싸세요.
지원 범위
| 항목 | 지원 |
|---|---|
| Node.js | 20 / 22 / 24 (CI가 세 버전 모두에서 npm run check를 실행) |
| pi | pi-package 확장 API (pi.extensions) |
| 원격 OS | POSIX 셸 + bash가 있는 Linux/macOS. Windows 원격은 미지원 |
| 인증 | SSH 키 / ssh-agent (BatchMode=yes) |
| 의존성 | pi-helper-core@^0.1.3 (transport seam) |
| 컨테이너 | Docker(docker exec)가 있는 원격 호스트, 선택 사항 |
0.y.z 단계입니다. 공개 도구 이름(ssh_*, /ssh-remote)은 유지하되, 설정 키·출력 data 필드·오류 코드는 마이너 릴리스에서 바뀔 수 있습니다. 버전 규칙은 SemVer를 따르고 변경 내역은 CHANGELOG.md에 남깁니다.
제한 (알려진 것)
- 비밀번호/passphrase 대화형 인증 미지원 (키 인증 + ssh-agent 전제)
- 원격
Match/Include/ProxyCommand미지원 - 컨테이너 실행은
docker exec단일 컨테이너 대상입니다(compose 서비스·여러 컨테이너 동시 지정 미지원) - DDS 도달성 트리아지(로컬↔원격 graph 비교)와 로그/rosbag 회수는 다음 단계입니다
- Windows 원격 미지원 (POSIX 경로·셸 전제)
개발
npm ci
npm test # 42 tests
npm run typecheck
npm run check # test + typecheck + format:check + pack-check
기여 규칙과 테스트 원칙은 CONTRIBUTING.md, 에이전트용 개발 규칙은 AGENTS.md에 있습니다. 취약점 신고는 SECURITY.md의 절차를 따릅니다.
릴리스
package.json 버전을 올리고 CHANGELOG를 옮긴 뒤 v<version> 태그를 푸시하면 .github/workflows/publish.yml이 npm provenance와 함께 배포합니다. 릴리스는 로컬이 아니라 CI에서만 수행합니다.
최초 배포만 수동이어야 합니다 (중요)
npm Trusted Publishing(OIDC)은 패키지의 최초 버전을 만들 수 없습니다. trusted publisher는 이미 존재하는 패키지에만 설정할 수 있기 때문에, 신규 패키지의 첫 배포는 OIDC로 실패하고 ENEEDAUTH가 나옵니다(npm/cli#8544, npm/documentation#1926). 인증 설정 오류처럼 보이지만 실제 원인은 그게 아닙니다.
최초 1회만:
npm login(패키지 소유자 계정)npm publish --access public— provenance 없이 올라갑니다- npmjs.com → 패키지 → Settings → Trusted Publisher → GitHub Actions
- Organization or user:
wkqco33 - Repository:
pi-ssh-helper - Workflow filename:
publish.yml(경로가 아니라 파일명만, 대소문자 구분) - Allowed actions:
npm publish
- Organization or user:
- 이후 태그 푸시는 OIDC + provenance로 자동 배포됩니다
부트스트랩 후에는 Settings → Publishing access에서 **"Require two-factor authentication and disallow tokens"**를 켜 두는 것이 권장됩니다.
워크플로에
actions/setup-node의registry-url을 쓰지 마세요._authToken=${NODE_AUTH_TOKEN}줄을 써 넣고, 토큰이 없을 때 빈 값으로 확장되어 OIDC 대신 일반 인증을 시도합니다(ENEEDAUTH/E404의 가장 흔한 원인).
상태
0.1.x는 npmpi-ssh-helper로 배포됩니다. 릴리스는 태그 push → CI(OIDC + provenance) 경로로만 수행합니다.latest버전은npm view pi-ssh-helper version으로 확인하세요.- TypeScript 소스를 그대로 실행합니다(빌드 단계 없음). pi 확장 생태계가 TS를 직접 로드하기 때문입니다.
pi-helper-core의 transport seam을 소비하는 첫 패키지입니다.