v1.8.23: upgrade ssh.py + skill for secure Claude Code integration
- Add --info (safe server details without credentials) - Add --set-note (update server notes for context) - Add --no-sudo flag documentation in skill - Fix --add leaking user@ip:port in output - Add WARNING to --list-full, forbid in skill - Add notes column to --list output - Update skill: portable path ~/.server-connections/, security rules, behavior docs - Update CLAUDE.md: security section, architecture, dev commands, doc references Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
149
CLAUDE.md
149
CLAUDE.md
@@ -1,8 +1,10 @@
|
||||
# CLAUDE.md — инструкции для Claude Code
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Основная цель проекта
|
||||
|
||||
ServerManager создан чтобы **Claude Code мог управлять серверами через глобальный скилл `/ssh`**, зная только алиасы серверов. Claude **никогда не видит логины и пароли** — скрипт `ssh.py` сам читает credentials из зашифрованного `servers.json`.
|
||||
ServerManager — Desktop GUI (CustomTkinter + Paramiko) для управления удалёнными серверами. Создан чтобы **Claude Code мог управлять серверами через глобальный скилл `/ssh`**, зная только алиасы.
|
||||
|
||||
```
|
||||
Claude Code → /ssh investor "uptime" → ssh.py → servers.json (encrypted) → SSH
|
||||
@@ -10,57 +12,60 @@ Claude Code → /ssh investor "uptime" → ssh.py → servers.json (encryp
|
||||
Видит только alias + команду Пароли остаются локально Результат
|
||||
```
|
||||
|
||||
## БЕЗОПАСНОСТЬ — ГЛАВНЫЙ ПРИНЦИП
|
||||
|
||||
**Claude НИКОГДА не видит и не должен видеть:**
|
||||
- IP-адреса серверов
|
||||
- Логины (user)
|
||||
- Пароли
|
||||
- Порты
|
||||
- TOTP-секреты
|
||||
|
||||
Всё это остаётся в зашифрованном `servers.json`. Скрипт `ssh.py` сам читает credentials и подключается. Claude работает **только через алиасы**.
|
||||
|
||||
**ЗАПРЕЩЕНО:**
|
||||
- Читать файлы в `~/.server-connections/` напрямую
|
||||
- Использовать `ssh.py --list-full` (выводит IP/логины)
|
||||
- Добавлять IP/логины/пароли в memory bank, контекст, логи
|
||||
|
||||
## Команды разработки
|
||||
|
||||
```bash
|
||||
# Запуск приложения
|
||||
python main.py
|
||||
|
||||
# Сборка exe (автоматически бампит patch-версию)
|
||||
python build.py
|
||||
|
||||
# Сборка без бампа версии
|
||||
python build.py --no-bump
|
||||
|
||||
# Очистка артефактов + сборка
|
||||
python build.py --clean
|
||||
|
||||
# Установка зависимостей
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Как пользоваться /ssh
|
||||
|
||||
```bash
|
||||
# Выполнить команду на сервере
|
||||
/ssh investor "uptime"
|
||||
/ssh main-ovh "systemctl status nginx"
|
||||
|
||||
# Список серверов (без паролей)
|
||||
/ssh --list
|
||||
|
||||
# Статус всех серверов
|
||||
/ssh --status
|
||||
|
||||
# Загрузить файл
|
||||
/ssh investor --upload /local/file /remote/path
|
||||
|
||||
# Скачать файл
|
||||
/ssh investor --download /remote/file /local/path
|
||||
|
||||
# Проверить доступность
|
||||
/ssh investor --ping
|
||||
/ssh investor "uptime" # Выполнить команду
|
||||
/ssh main-ovh "systemctl status nginx" # Команда на другом сервере
|
||||
/ssh --list # Список серверов (без паролей)
|
||||
/ssh --status # Статус всех серверов
|
||||
/ssh investor --upload /local/file /remote/ # Загрузить файл
|
||||
/ssh investor --download /remote/file /local/ # Скачать файл
|
||||
/ssh investor --ping # Проверить доступность
|
||||
```
|
||||
|
||||
## Как настроить на новой машине
|
||||
|
||||
1. Клонировать: `git clone https://git.sensey24.ru/aibot777/server-manager.git`
|
||||
2. `pip install -r requirements.txt`
|
||||
3. `python main.py` → вкладка Setup → "Install Everything"
|
||||
4. Добавить серверы через GUI → Claude Code сразу видит их через `/ssh --list`
|
||||
|
||||
Setup устанавливает:
|
||||
- `ssh.py` → `~/.server-connections/`
|
||||
- `encryption.py` → `~/.server-connections/`
|
||||
- `/ssh` skill → `~/.claude/commands/ssh.md`
|
||||
- SSH-ключ (ed25519)
|
||||
|
||||
## Версионирование
|
||||
|
||||
**Автоматическое.** `build.py` сам инкрементирует patch-версию при каждой сборке.
|
||||
**Автоматическое.** `build.py` инкрементирует patch в `version.py` → `__version__` при каждой сборке.
|
||||
|
||||
```bash
|
||||
python build.py # автобамп 1.8.9 → 1.8.10, сборка
|
||||
python build.py --no-bump # сборка без изменения версии
|
||||
python build.py --clean # очистка артефактов + сборка
|
||||
```
|
||||
Semver: **MAJOR** (ломающие) | **MINOR** (новая фича) | **PATCH** (автобамп при сборке)
|
||||
|
||||
Файл версии: `version.py` → `__version__`
|
||||
|
||||
Semver: **MAJOR** (ломающие) | **MINOR** (новая фича) | **PATCH** (багфикс — автобамп)
|
||||
|
||||
Для minor/major бампа — вручную изменить version.py перед сборкой.
|
||||
Для minor/major бампа — вручную изменить `version.py` перед `python build.py --no-bump`.
|
||||
|
||||
## Git — коммит и пуш
|
||||
|
||||
@@ -78,23 +83,23 @@ git push sensey master
|
||||
|
||||
```
|
||||
ServerManager/
|
||||
├── main.py # Entry point
|
||||
├── version.py # Version (auto-bumped by build.py)
|
||||
├── main.py # Entry point → gui/app.py
|
||||
├── version.py # __version__ (auto-bumped by build.py)
|
||||
├── build.py # PyInstaller + auto version bump
|
||||
├── core/ # Business logic
|
||||
│ ├── server_store.py # CRUD + encrypted JSON + backups
|
||||
│ ├── encryption.py # Fernet encryption
|
||||
│ ├── ssh_client.py # Paramiko SSH/SFTP/ShellSession
|
||||
│ ├── server_store.py # CRUD + encrypted JSON + observer pattern + backups
|
||||
│ ├── encryption.py # Fernet symmetric encryption
|
||||
│ ├── ssh_client.py # Paramiko: ShellSession + SFTPSession
|
||||
│ ├── session_pool.py # LRU session pool (max 5 concurrent)
|
||||
│ ├── claude_setup.py # Claude Code integration installer
|
||||
│ ├── status_checker.py # Background monitoring
|
||||
│ ├── status_checker.py # Background monitoring (every 60s)
|
||||
│ ├── totp.py # TOTP/2FA (pyotp)
|
||||
│ ├── i18n.py # EN/RU/ZH translations
|
||||
│ ├── i18n.py # EN/RU/ZH translations, t(key) API
|
||||
│ └── logger.py # Rotating file logger
|
||||
├── gui/ # CustomTkinter UI
|
||||
│ ├── app.py # Main window
|
||||
│ ├── sidebar.py # Server list + session indicators
|
||||
│ ├── tabs/ # Terminal, Files, Info, Keys, 2FA, Setup
|
||||
│ ├── app.py # Main window: sidebar + tabview
|
||||
│ ├── sidebar.py # Server list + session indicators + search
|
||||
│ ├── tabs/ # Terminal, Files, Info, Keys, TOTP, Setup
|
||||
│ └── widgets/ # TerminalWidget (pyte), FileList, StatusBadge
|
||||
├── tools/ # CLI tools (installed to ~/.server-connections/)
|
||||
│ ├── ssh.py # SSH utility for Claude Code
|
||||
@@ -102,11 +107,35 @@ ServerManager/
|
||||
└── releases/ # Built executables
|
||||
```
|
||||
|
||||
## Ключевые особенности терминала
|
||||
|
||||
- **pyte** эмулятор + tkinter Text виджет
|
||||
**Ключевые паттерны:**
|
||||
- Терминал использует **pyte** для эмуляции VT-терминала поверх tkinter Text виджета
|
||||
- **Session pool** — сессии живут при переключении серверов, буфер сохраняется через pickle
|
||||
- **Auto-sudo** — детекция `[sudo] password for` в выводе, автоматическая отправка пароля
|
||||
- **Ctrl+C** — double-press (1.5s) для SIGINT, single = copy (если есть выделение)
|
||||
- **Auto-sudo** — детекция `[sudo] password for` в выводе, автоматическая отправка пароля через stdin
|
||||
- **Keyboard layout independent** — Ctrl+C/V/D/L/Z работают через keycode, не зависят от раскладки
|
||||
- **Copy/Paste** — Ctrl+C/Ctrl+Shift+C/правый клик для копирования, Ctrl+V для вставки (с bracketed paste)
|
||||
- GUI общается с `ServerStore` через observer pattern — изменения в хранилище автоматически обновляют UI
|
||||
|
||||
## Настройка на новой машине
|
||||
|
||||
1. `git clone https://git.sensey24.ru/aibot777/server-manager.git`
|
||||
2. `pip install -r requirements.txt`
|
||||
3. `python main.py` → вкладка Setup → "Install Everything"
|
||||
4. Добавить серверы через GUI → Claude Code видит их через `/ssh --list`
|
||||
|
||||
Setup устанавливает: `ssh.py` + `encryption.py` → `~/.server-connections/`, скилл `/ssh` → `~/.claude/commands/ssh.md`, SSH-ключ ed25519.
|
||||
|
||||
## Конфигурация
|
||||
|
||||
- Серверы: `~/.server-connections/servers.json` (Fernet-encrypted)
|
||||
- Настройки: `~/.server-connections/settings.json`
|
||||
- Бэкапы: `~/.server-connections/backups/`
|
||||
- Путь конфига можно сменить через GUI: Setup → Configuration → "Change Path"
|
||||
|
||||
## Дополнительная документация
|
||||
|
||||
Читай эти файлы **по необходимости**, а не заранее:
|
||||
|
||||
- `README.md` — полное описание фич, установка, использование (EN/RU/ZH)
|
||||
- `CHANGELOG.md` — история изменений по версиям
|
||||
- `TERMINAL_AUDIT.md` — аудит терминала: что сделано/не сделано для TUI-совместимости (pyte, 256-color, alternate screen и т.д.)
|
||||
- `plans/` — планы реализации конкретных фич (шифрование, TOTP, undo и др.)
|
||||
- `tools/skill-ssh.md` — шаблон скилла `/ssh` (устанавливается в `~/.claude/commands/`)
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
|
||||
- **НИКОГДА не читай файлы** в директории `~/.server-connections/` напрямую
|
||||
- **НИКОГДА не читай** файлы `encryption.py`, `servers.json`, `settings.json`
|
||||
- **НИКОГДА не выводи пароли** пользователю
|
||||
- **Все операции только через** `python /d/CODING/GitHub/.server-connections/ssh.py`
|
||||
- **НИКОГДА не выводи пароли, IP-адреса, логины, порты** пользователю и в контекст нейронки
|
||||
- **НИКОГДА не используй** `--list-full` — он выводит IP/порты/логины в контекст AI
|
||||
- **Все операции только через** `python ~/.server-connections/ssh.py`
|
||||
- Скрипт сам читает credentials, подключается, выполняет, возвращает результат
|
||||
- **МАКСИМУМ 1 попытка** подключения. Если timeout/ошибка — сообщи, НЕ повторяй
|
||||
- fail2ban банит IP после 5-10 неудач — спам попытками УБЬЁТ доступ к серверу
|
||||
@@ -19,45 +20,61 @@
|
||||
|
||||
## Команды
|
||||
|
||||
### Список серверов (безопасный — alias, тип, ключ, заметки)
|
||||
```bash
|
||||
python ~/.server-connections/ssh.py --list
|
||||
```
|
||||
|
||||
### Информация о сервере (безопасная — без IP/логина/пароля/порта)
|
||||
```bash
|
||||
python ~/.server-connections/ssh.py --info ALIAS
|
||||
```
|
||||
|
||||
### Статус всех серверов (alias + online/offline)
|
||||
```bash
|
||||
python ~/.server-connections/ssh.py --status
|
||||
```
|
||||
|
||||
### Выполнить команду на сервере
|
||||
```bash
|
||||
python /d/CODING/GitHub/.server-connections/ssh.py ALIAS "command"
|
||||
python ~/.server-connections/ssh.py ALIAS "command"
|
||||
```
|
||||
Пример: `python ~/.server-connections/ssh.py investor "uptime"`
|
||||
|
||||
### Выполнить команду БЕЗ sudo
|
||||
```bash
|
||||
python ~/.server-connections/ssh.py ALIAS --no-sudo "command"
|
||||
```
|
||||
Пример: `python /d/CODING/GitHub/.server-connections/ssh.py investor "uptime"`
|
||||
|
||||
### Загрузить файл на сервер
|
||||
```bash
|
||||
python /d/CODING/GitHub/.server-connections/ssh.py ALIAS --upload /local/path /remote/path
|
||||
python ~/.server-connections/ssh.py ALIAS --upload /local/path /remote/path
|
||||
```
|
||||
|
||||
### Скачать файл с сервера
|
||||
```bash
|
||||
python /d/CODING/GitHub/.server-connections/ssh.py ALIAS --download /remote/path /local/path
|
||||
python ~/.server-connections/ssh.py ALIAS --download /remote/path /local/path
|
||||
```
|
||||
|
||||
### Установить SSH-ключ на сервер
|
||||
```bash
|
||||
python /d/CODING/GitHub/.server-connections/ssh.py ALIAS --install-key
|
||||
python ~/.server-connections/ssh.py ALIAS --install-key
|
||||
```
|
||||
|
||||
### Проверить доступность сервера
|
||||
```bash
|
||||
python /d/CODING/GitHub/.server-connections/ssh.py ALIAS --ping
|
||||
python ~/.server-connections/ssh.py ALIAS --ping
|
||||
```
|
||||
|
||||
### Список серверов (только алиасы, без IP/паролей)
|
||||
### Обновить заметки сервера
|
||||
```bash
|
||||
python /d/CODING/GitHub/.server-connections/ssh.py --list
|
||||
```
|
||||
|
||||
### Статус всех серверов (только алиасы + online/offline)
|
||||
```bash
|
||||
python /d/CODING/GitHub/.server-connections/ssh.py --status
|
||||
python ~/.server-connections/ssh.py --set-note ALIAS "описание сервера"
|
||||
```
|
||||
Используй чтобы сохранить контекст: что на сервере работает, для чего он нужен.
|
||||
|
||||
### Удалить сервер
|
||||
```bash
|
||||
python /d/CODING/GitHub/.server-connections/ssh.py --remove ALIAS
|
||||
python ~/.server-connections/ssh.py --remove ALIAS
|
||||
```
|
||||
**Спроси подтверждение у пользователя перед удалением!**
|
||||
|
||||
@@ -66,6 +83,13 @@ python /d/CODING/GitHub/.server-connections/ssh.py --remove ALIAS
|
||||
unset SSH_ASKPASS && unset DISPLAY && ssh ALIAS "command"
|
||||
```
|
||||
|
||||
## Поведение
|
||||
|
||||
- **Auto-sudo**: если user на сервере не root — команды автоматически оборачиваются в `sudo -S`, пароль подаётся через stdin. Тебе НЕ нужно добавлять `sudo` в команду
|
||||
- **--no-sudo**: если команда не требует root (например `ls`, `cat`), используй `--no-sudo` для скорости
|
||||
- **Timeout**: 120 секунд на команду, 15 секунд на подключение
|
||||
- **SSH-ключ**: пробуется первым, fallback на пароль если ключ не подходит
|
||||
|
||||
## Правила
|
||||
|
||||
- Отвечай на русском языке
|
||||
@@ -73,3 +97,4 @@ unset SSH_ASKPASS && unset DISPLAY && ssh ALIAS "command"
|
||||
- При ошибках — объясняй причину и предлагай решение
|
||||
- Если timeout — предложи проверить VPN/firewall/панель хостера
|
||||
- Файлы создаваемые на сервере должны иметь права 664 (owner+group rw)
|
||||
- При вопросе о серверах — СНАЧАЛА `--list`, потом `--info ALIAS` если нужны детали
|
||||
|
||||
50
tools/ssh.py
50
tools/ssh.py
@@ -12,6 +12,8 @@ Usage:
|
||||
python ssh.py ALIAS --ping
|
||||
python ssh.py --list
|
||||
python ssh.py --status
|
||||
python ssh.py --info ALIAS # full info (no passwords)
|
||||
python ssh.py --set-note ALIAS "desc" # update server notes
|
||||
python ssh.py --add ALIAS IP PORT USER PASSWORD [--note "desc"]
|
||||
python ssh.py --remove ALIAS
|
||||
"""
|
||||
@@ -229,6 +231,9 @@ def ping_server(server: dict):
|
||||
def list_servers(full=False):
|
||||
_, servers = load_servers()
|
||||
if full:
|
||||
# WARNING: full mode shows sensitive data (IP, port, user)
|
||||
# Only for local/manual use, NEVER through AI API
|
||||
print("WARNING: Full mode — contains sensitive data. Do NOT pipe to AI.")
|
||||
print(f"{'Alias':<20} {'IP':<20} {'Port':<8} {'User':<10} {'Key':<6}")
|
||||
print("-" * 64)
|
||||
for alias, s in servers.items():
|
||||
@@ -236,12 +241,31 @@ def list_servers(full=False):
|
||||
print(f"{alias:<20} {s['ip']:<20} {s.get('port', 22):<8} {s.get('user', 'root'):<10} {has_key:<6}")
|
||||
else:
|
||||
# Safe mode: only aliases (no IPs, ports, users)
|
||||
print(f"{'Alias':<20} {'Type':<10} {'Key':<6}")
|
||||
print("-" * 36)
|
||||
print(f"{'Alias':<20} {'Type':<10} {'Key':<6} {'Notes'}")
|
||||
print("-" * 70)
|
||||
for alias, s in servers.items():
|
||||
has_key = "yes" if os.path.exists(SSH_KEY_PATH) else "no"
|
||||
stype = s.get("type", "ssh")
|
||||
print(f"{alias:<20} {stype:<10} {has_key:<6}")
|
||||
notes = s.get("notes", "")
|
||||
print(f"{alias:<20} {stype:<10} {has_key:<6} {notes}")
|
||||
|
||||
|
||||
def server_info(alias: str):
|
||||
"""Show server info safe for AI context — NO ip, user, password, port, totp_secret."""
|
||||
_, servers = load_servers()
|
||||
if alias not in servers:
|
||||
print(f"Unknown: {alias}. Available: {', '.join(servers.keys())}")
|
||||
sys.exit(1)
|
||||
s = servers[alias]
|
||||
has_key = "yes" if os.path.exists(SSH_KEY_PATH) else "no"
|
||||
print(f"Alias: {s['alias']}")
|
||||
print(f"Type: {s.get('type', 'ssh')}")
|
||||
print(f"Key: {has_key}")
|
||||
print(f"Auth: {s.get('auth', 'password')}")
|
||||
print(f"2FA: {'yes' if s.get('totp_secret') else 'no'}")
|
||||
notes = s.get("notes", "")
|
||||
if notes:
|
||||
print(f"Notes: {notes}")
|
||||
|
||||
|
||||
def check_status():
|
||||
@@ -283,7 +307,7 @@ def add_server(args):
|
||||
data["servers"].append(new_server)
|
||||
save_servers(data)
|
||||
update_ssh_config(alias, ip, port, user)
|
||||
print(f"Added: {alias} ({user}@{ip}:{port})")
|
||||
print(f"Added: {alias}")
|
||||
|
||||
try:
|
||||
install_key(new_server)
|
||||
@@ -291,6 +315,20 @@ def add_server(args):
|
||||
print(f"Warning: key not installed ({e}). Run: ssh.py {alias} --install-key")
|
||||
|
||||
|
||||
def set_note(alias: str, note: str):
|
||||
"""Update server notes — safe for AI (no credentials exposed)."""
|
||||
data, servers = load_servers()
|
||||
if alias not in servers:
|
||||
print(f"Unknown: {alias}. Available: {', '.join(servers.keys())}")
|
||||
sys.exit(1)
|
||||
for s in data["servers"]:
|
||||
if s["alias"] == alias:
|
||||
s["notes"] = note
|
||||
break
|
||||
save_servers(data)
|
||||
print(f"OK: notes updated for {alias}")
|
||||
|
||||
|
||||
def remove_server(alias: str):
|
||||
data, servers = load_servers()
|
||||
if alias not in servers:
|
||||
@@ -348,6 +386,10 @@ def main():
|
||||
list_servers(full=True); sys.exit(0)
|
||||
if cmd == "--status":
|
||||
check_status(); sys.exit(0)
|
||||
if cmd == "--info" and len(sys.argv) >= 3:
|
||||
server_info(sys.argv[2]); sys.exit(0)
|
||||
if cmd == "--set-note" and len(sys.argv) >= 4:
|
||||
set_note(sys.argv[2], sys.argv[3]); sys.exit(0)
|
||||
if cmd == "--add":
|
||||
add_server(sys.argv[2:]); sys.exit(0)
|
||||
if cmd == "--remove" and len(sys.argv) >= 3:
|
||||
|
||||
Reference in New Issue
Block a user