gpu-server-setup 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gpu_server_setup-0.4.0/LICENSE +9 -0
- gpu_server_setup-0.4.0/PKG-INFO +248 -0
- gpu_server_setup-0.4.0/README.md +232 -0
- gpu_server_setup-0.4.0/gpu_server_setup.egg-info/PKG-INFO +248 -0
- gpu_server_setup-0.4.0/gpu_server_setup.egg-info/SOURCES.txt +16 -0
- gpu_server_setup-0.4.0/gpu_server_setup.egg-info/dependency_links.txt +1 -0
- gpu_server_setup-0.4.0/gpu_server_setup.egg-info/entry_points.txt +2 -0
- gpu_server_setup-0.4.0/gpu_server_setup.egg-info/requires.txt +7 -0
- gpu_server_setup-0.4.0/gpu_server_setup.egg-info/top_level.txt +1 -0
- gpu_server_setup-0.4.0/gpu_setup/__init__.py +3 -0
- gpu_server_setup-0.4.0/gpu_setup/__version__.py +1 -0
- gpu_server_setup-0.4.0/gpu_setup/cli.py +138 -0
- gpu_server_setup-0.4.0/gpu_setup/config.py +17 -0
- gpu_server_setup-0.4.0/gpu_setup/core.py +331 -0
- gpu_server_setup-0.4.0/gpu_setup/rich_console.py +39 -0
- gpu_server_setup-0.4.0/gpu_setup/utils.py +22 -0
- gpu_server_setup-0.4.0/pyproject.toml +18 -0
- gpu_server_setup-0.4.0/setup.cfg +4 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gpu-server-setup
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Один клик — удалённый GPU-сервер с Ollama + VS Code Remote
|
|
5
|
+
Author: The Fool
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: typer[all]>=0.12
|
|
11
|
+
Requires-Dist: rich>=13.7
|
|
12
|
+
Requires-Dist: paramiko>=3.4
|
|
13
|
+
Requires-Dist: pydantic-settings>=2.5
|
|
14
|
+
Requires-Dist: tomli>=2.0; python_version >= "3.9"
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
```markdown
|
|
18
|
+
# Gpu-setup — один клик до удалённого GPU-сервера с Ollama и VS Code Remote
|
|
19
|
+
|
|
20
|
+
Автоматическая настройка SSH, установка [Ollama](https://ollama.com/), загрузка LLM-моделей и открытие VS Code Remote на удалённом GPU-сервере — всё одной командой.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Возможности
|
|
25
|
+
|
|
26
|
+
- **Генерация SSH-ключей** (ed25519 / RSA) и автоматическое копирование публичного ключа на сервер.
|
|
27
|
+
- **Настройка `~/.ssh/config`** с алиасом для удобного подключения.
|
|
28
|
+
- **Установка Ollama** (через официальный скрипт) с запуском сервиса.
|
|
29
|
+
- **Загрузка моделей** (например, `llama3.2`, `gemma2`) с живым выводом прогресса.
|
|
30
|
+
- **Автоматический запуск VS Code Remote** (если установлен `code` CLI).
|
|
31
|
+
- **Режим `--dry-run`** для безопасного просмотра плана действий.
|
|
32
|
+
- **Красивый вывод** с помощью `rich`.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Предварительные требования
|
|
37
|
+
|
|
38
|
+
- Python 3.8+ (рекомендуется 3.10)
|
|
39
|
+
- Удалённый сервер с Linux (Ubuntu/Debian/CentOS) и предустановленным Python
|
|
40
|
+
- Доступ по SSH с паролем (для первого копирования ключа)
|
|
41
|
+
- На локальной машине:
|
|
42
|
+
- `ssh-keygen` (обычно уже есть)
|
|
43
|
+
- `ssh` клиент
|
|
44
|
+
- (опционально) `code` для VS Code Remote (если планируете открывать редактор)
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Установка
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Клонируем репозиторий
|
|
52
|
+
git clone https://github.com/your-username/gpu-setup.git
|
|
53
|
+
cd gpu-setup
|
|
54
|
+
|
|
55
|
+
# Устанавливаем в виртуальное окружение (рекомендуется)
|
|
56
|
+
python -m venv venv
|
|
57
|
+
source venv/bin/activate # или venv\Scripts\activate на Windows
|
|
58
|
+
|
|
59
|
+
# Устанавливаем зависимости
|
|
60
|
+
pip install -r requirements.txt
|
|
61
|
+
|
|
62
|
+
# Устанавливаем пакет в режиме разработки (опционально)
|
|
63
|
+
pip install -e .
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
После этого команда `gpu-setup` будет доступна в терминале.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Использование
|
|
71
|
+
|
|
72
|
+
Общий синтаксис:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
gpu-setup [команда] [опции]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Доступные команды
|
|
79
|
+
|
|
80
|
+
| Команда | Описание |
|
|
81
|
+
|-------------------|----------|
|
|
82
|
+
| `setup` | **Полная настройка:** SSH, Ollama, модель, VS Code |
|
|
83
|
+
| `ssh` | Только настройка SSH-ключей и алиаса |
|
|
84
|
+
| `ollama-install` | Установка Ollama на сервер |
|
|
85
|
+
| `model-pull` | Загрузка указанной модели (после установки Ollama) |
|
|
86
|
+
| `vscode` | Открытие VS Code Remote (если `code` установлен) |
|
|
87
|
+
| `plan` | Показать план действий без выполнения (dry-run) |
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Подробное описание команд
|
|
92
|
+
|
|
93
|
+
### `gpu-setup setup`
|
|
94
|
+
|
|
95
|
+
Выполняет все шаги: генерация ключа, копирование, настройка SSH-конфига, установка Ollama, загрузка модели, открытие VS Code.
|
|
96
|
+
|
|
97
|
+
**Опции:**
|
|
98
|
+
|
|
99
|
+
- `--ip` (обязательный) — IP сервера
|
|
100
|
+
- `--user` (обязательный) — имя пользователя
|
|
101
|
+
- `--password` (обязательный для первого подключения) — пароль (можно ввести интерактивно)
|
|
102
|
+
- `--model` — модель для скачивания (например `llama3.2`)
|
|
103
|
+
- `--install-ollama` / `--no-install` — устанавливать Ollama (по умолчанию `--install-ollama`)
|
|
104
|
+
- `--no-vscode` — не открывать VS Code (по умолчанию открывает)
|
|
105
|
+
- `--dry-run` — только показать план
|
|
106
|
+
- `--key-type` — тип ключа: `ed25519` (по умолчанию) или `rsa`
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
### `gpu-setup ssh`
|
|
111
|
+
|
|
112
|
+
Только настройка SSH-доступа по ключу.
|
|
113
|
+
|
|
114
|
+
**Опции:**
|
|
115
|
+
|
|
116
|
+
- `--ip` (обязательный)
|
|
117
|
+
- `--user` (обязательный)
|
|
118
|
+
- `--password` (обязательный для первого копирования)
|
|
119
|
+
- `--dry-run`
|
|
120
|
+
- `--key-type`
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### `gpu-setup ollama-install`
|
|
125
|
+
|
|
126
|
+
Устанавливает Ollama на сервер (требуется пароль для sudo).
|
|
127
|
+
|
|
128
|
+
**Опции:**
|
|
129
|
+
|
|
130
|
+
- `--ip` (обязательный)
|
|
131
|
+
- `--user` (обязательный)
|
|
132
|
+
- `--password` (обязательный, т.к. нужен sudo)
|
|
133
|
+
- `--dry-run`
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
### `gpu-setup model-pull MODEL`
|
|
138
|
+
|
|
139
|
+
Скачивает указанную модель через Ollama. Если сервис Ollama не запущен, пытается запустить его автоматически.
|
|
140
|
+
|
|
141
|
+
**Аргументы:**
|
|
142
|
+
|
|
143
|
+
- `MODEL` — имя модели (например `llama3.2`)
|
|
144
|
+
|
|
145
|
+
**Опции:**
|
|
146
|
+
|
|
147
|
+
- `--ip` (обязательный)
|
|
148
|
+
- `--user` (обязательный)
|
|
149
|
+
- `--password` (необязателен, если SSH уже настроен; нужен только для запуска сервиса)
|
|
150
|
+
- `--dry-run`
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
### `gpu-setup vscode`
|
|
155
|
+
|
|
156
|
+
Открывает текущую сессию в VS Code Remote.
|
|
157
|
+
|
|
158
|
+
**Опции:**
|
|
159
|
+
|
|
160
|
+
- `--ip` (обязательный)
|
|
161
|
+
- `--user` (обязательный)
|
|
162
|
+
- `--password` (не требуется, если ключ настроен)
|
|
163
|
+
- `--dry-run`
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
### `gpu-setup plan`
|
|
168
|
+
|
|
169
|
+
Показывает, какие шаги будут выполнены при вызове `setup` с указанными параметрами.
|
|
170
|
+
|
|
171
|
+
**Опции:**
|
|
172
|
+
|
|
173
|
+
- `--ip` (обязательный)
|
|
174
|
+
- `--user` (обязательный)
|
|
175
|
+
- `--model`
|
|
176
|
+
- `--install-ollama` / `--no-install`
|
|
177
|
+
- `--no-vscode`
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Примеры использования
|
|
182
|
+
|
|
183
|
+
### 1. Полная настройка (самый частый вариант)
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
gpu-setup setup --ip 195.208.16.2 --user user --password sWDoAVym --model llama3.2
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### 2. Только SSH-ключи + config
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
gpu-setup ssh --ip 195.208.16.2 --user user --password sWDoAVym
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### 3. Только установить Ollama
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
gpu-setup ollama-install --ip 195.208.16.2 --user user --password sWDoAVym
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### 4. Только скачать модель (пароль уже не нужен, если SSH настроен)
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
gpu-setup model-pull llama3.2 --ip 195.208.16.2 --user user
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### 5. Только открыть VS Code Remote
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
gpu-setup vscode --ip 195.208.16.2 --user user
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### 6. Посмотреть план перед запуском
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
gpu-setup plan --ip 195.208.16.2 --user user --model llama3.2
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Dry-run
|
|
222
|
+
|
|
223
|
+
Любая команда поддерживает флаг `--dry-run`.
|
|
224
|
+
В этом режиме ничего не изменяется, а только выводится, что было бы сделано.
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
gpu-setup setup --ip 195.208.16.2 --user user --dry-run
|
|
228
|
+
```
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Устранение неполадок
|
|
232
|
+
|
|
233
|
+
- **`ssh: Could not resolve hostname`** — проверьте IP и доступность сервера.
|
|
234
|
+
- **Ошибка при копировании ключа** — убедитесь, что пароль верный и на сервере разрешена аутентификация по паролю (`PasswordAuthentication yes` в `/etc/ssh/sshd_config`).
|
|
235
|
+
- **Ollama не устанавливается** — возможно, требуется вручную разрешить `sudo` без пароля или передать пароль в опции.
|
|
236
|
+
- **VS Code не открывается** — установите CLI-команду `code` (в VS Code: `Shift+Ctrl+P` → `Install 'code' command in PATH`).
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Лицензия
|
|
241
|
+
|
|
242
|
+
MIT License. См. файл [LICENSE](LICENSE).
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Вклад
|
|
247
|
+
|
|
248
|
+
PR и issues приветствуются!
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
```markdown
|
|
2
|
+
# Gpu-setup — один клик до удалённого GPU-сервера с Ollama и VS Code Remote
|
|
3
|
+
|
|
4
|
+
Автоматическая настройка SSH, установка [Ollama](https://ollama.com/), загрузка LLM-моделей и открытие VS Code Remote на удалённом GPU-сервере — всё одной командой.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Возможности
|
|
9
|
+
|
|
10
|
+
- **Генерация SSH-ключей** (ed25519 / RSA) и автоматическое копирование публичного ключа на сервер.
|
|
11
|
+
- **Настройка `~/.ssh/config`** с алиасом для удобного подключения.
|
|
12
|
+
- **Установка Ollama** (через официальный скрипт) с запуском сервиса.
|
|
13
|
+
- **Загрузка моделей** (например, `llama3.2`, `gemma2`) с живым выводом прогресса.
|
|
14
|
+
- **Автоматический запуск VS Code Remote** (если установлен `code` CLI).
|
|
15
|
+
- **Режим `--dry-run`** для безопасного просмотра плана действий.
|
|
16
|
+
- **Красивый вывод** с помощью `rich`.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Предварительные требования
|
|
21
|
+
|
|
22
|
+
- Python 3.8+ (рекомендуется 3.10)
|
|
23
|
+
- Удалённый сервер с Linux (Ubuntu/Debian/CentOS) и предустановленным Python
|
|
24
|
+
- Доступ по SSH с паролем (для первого копирования ключа)
|
|
25
|
+
- На локальной машине:
|
|
26
|
+
- `ssh-keygen` (обычно уже есть)
|
|
27
|
+
- `ssh` клиент
|
|
28
|
+
- (опционально) `code` для VS Code Remote (если планируете открывать редактор)
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Установка
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Клонируем репозиторий
|
|
36
|
+
git clone https://github.com/your-username/gpu-setup.git
|
|
37
|
+
cd gpu-setup
|
|
38
|
+
|
|
39
|
+
# Устанавливаем в виртуальное окружение (рекомендуется)
|
|
40
|
+
python -m venv venv
|
|
41
|
+
source venv/bin/activate # или venv\Scripts\activate на Windows
|
|
42
|
+
|
|
43
|
+
# Устанавливаем зависимости
|
|
44
|
+
pip install -r requirements.txt
|
|
45
|
+
|
|
46
|
+
# Устанавливаем пакет в режиме разработки (опционально)
|
|
47
|
+
pip install -e .
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
После этого команда `gpu-setup` будет доступна в терминале.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Использование
|
|
55
|
+
|
|
56
|
+
Общий синтаксис:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
gpu-setup [команда] [опции]
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Доступные команды
|
|
63
|
+
|
|
64
|
+
| Команда | Описание |
|
|
65
|
+
|-------------------|----------|
|
|
66
|
+
| `setup` | **Полная настройка:** SSH, Ollama, модель, VS Code |
|
|
67
|
+
| `ssh` | Только настройка SSH-ключей и алиаса |
|
|
68
|
+
| `ollama-install` | Установка Ollama на сервер |
|
|
69
|
+
| `model-pull` | Загрузка указанной модели (после установки Ollama) |
|
|
70
|
+
| `vscode` | Открытие VS Code Remote (если `code` установлен) |
|
|
71
|
+
| `plan` | Показать план действий без выполнения (dry-run) |
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Подробное описание команд
|
|
76
|
+
|
|
77
|
+
### `gpu-setup setup`
|
|
78
|
+
|
|
79
|
+
Выполняет все шаги: генерация ключа, копирование, настройка SSH-конфига, установка Ollama, загрузка модели, открытие VS Code.
|
|
80
|
+
|
|
81
|
+
**Опции:**
|
|
82
|
+
|
|
83
|
+
- `--ip` (обязательный) — IP сервера
|
|
84
|
+
- `--user` (обязательный) — имя пользователя
|
|
85
|
+
- `--password` (обязательный для первого подключения) — пароль (можно ввести интерактивно)
|
|
86
|
+
- `--model` — модель для скачивания (например `llama3.2`)
|
|
87
|
+
- `--install-ollama` / `--no-install` — устанавливать Ollama (по умолчанию `--install-ollama`)
|
|
88
|
+
- `--no-vscode` — не открывать VS Code (по умолчанию открывает)
|
|
89
|
+
- `--dry-run` — только показать план
|
|
90
|
+
- `--key-type` — тип ключа: `ed25519` (по умолчанию) или `rsa`
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
### `gpu-setup ssh`
|
|
95
|
+
|
|
96
|
+
Только настройка SSH-доступа по ключу.
|
|
97
|
+
|
|
98
|
+
**Опции:**
|
|
99
|
+
|
|
100
|
+
- `--ip` (обязательный)
|
|
101
|
+
- `--user` (обязательный)
|
|
102
|
+
- `--password` (обязательный для первого копирования)
|
|
103
|
+
- `--dry-run`
|
|
104
|
+
- `--key-type`
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
### `gpu-setup ollama-install`
|
|
109
|
+
|
|
110
|
+
Устанавливает Ollama на сервер (требуется пароль для sudo).
|
|
111
|
+
|
|
112
|
+
**Опции:**
|
|
113
|
+
|
|
114
|
+
- `--ip` (обязательный)
|
|
115
|
+
- `--user` (обязательный)
|
|
116
|
+
- `--password` (обязательный, т.к. нужен sudo)
|
|
117
|
+
- `--dry-run`
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
### `gpu-setup model-pull MODEL`
|
|
122
|
+
|
|
123
|
+
Скачивает указанную модель через Ollama. Если сервис Ollama не запущен, пытается запустить его автоматически.
|
|
124
|
+
|
|
125
|
+
**Аргументы:**
|
|
126
|
+
|
|
127
|
+
- `MODEL` — имя модели (например `llama3.2`)
|
|
128
|
+
|
|
129
|
+
**Опции:**
|
|
130
|
+
|
|
131
|
+
- `--ip` (обязательный)
|
|
132
|
+
- `--user` (обязательный)
|
|
133
|
+
- `--password` (необязателен, если SSH уже настроен; нужен только для запуска сервиса)
|
|
134
|
+
- `--dry-run`
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
### `gpu-setup vscode`
|
|
139
|
+
|
|
140
|
+
Открывает текущую сессию в VS Code Remote.
|
|
141
|
+
|
|
142
|
+
**Опции:**
|
|
143
|
+
|
|
144
|
+
- `--ip` (обязательный)
|
|
145
|
+
- `--user` (обязательный)
|
|
146
|
+
- `--password` (не требуется, если ключ настроен)
|
|
147
|
+
- `--dry-run`
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
### `gpu-setup plan`
|
|
152
|
+
|
|
153
|
+
Показывает, какие шаги будут выполнены при вызове `setup` с указанными параметрами.
|
|
154
|
+
|
|
155
|
+
**Опции:**
|
|
156
|
+
|
|
157
|
+
- `--ip` (обязательный)
|
|
158
|
+
- `--user` (обязательный)
|
|
159
|
+
- `--model`
|
|
160
|
+
- `--install-ollama` / `--no-install`
|
|
161
|
+
- `--no-vscode`
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Примеры использования
|
|
166
|
+
|
|
167
|
+
### 1. Полная настройка (самый частый вариант)
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
gpu-setup setup --ip 195.208.16.2 --user user --password sWDoAVym --model llama3.2
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### 2. Только SSH-ключи + config
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
gpu-setup ssh --ip 195.208.16.2 --user user --password sWDoAVym
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### 3. Только установить Ollama
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
gpu-setup ollama-install --ip 195.208.16.2 --user user --password sWDoAVym
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### 4. Только скачать модель (пароль уже не нужен, если SSH настроен)
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
gpu-setup model-pull llama3.2 --ip 195.208.16.2 --user user
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### 5. Только открыть VS Code Remote
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
gpu-setup vscode --ip 195.208.16.2 --user user
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### 6. Посмотреть план перед запуском
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
gpu-setup plan --ip 195.208.16.2 --user user --model llama3.2
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Dry-run
|
|
206
|
+
|
|
207
|
+
Любая команда поддерживает флаг `--dry-run`.
|
|
208
|
+
В этом режиме ничего не изменяется, а только выводится, что было бы сделано.
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
gpu-setup setup --ip 195.208.16.2 --user user --dry-run
|
|
212
|
+
```
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Устранение неполадок
|
|
216
|
+
|
|
217
|
+
- **`ssh: Could not resolve hostname`** — проверьте IP и доступность сервера.
|
|
218
|
+
- **Ошибка при копировании ключа** — убедитесь, что пароль верный и на сервере разрешена аутентификация по паролю (`PasswordAuthentication yes` в `/etc/ssh/sshd_config`).
|
|
219
|
+
- **Ollama не устанавливается** — возможно, требуется вручную разрешить `sudo` без пароля или передать пароль в опции.
|
|
220
|
+
- **VS Code не открывается** — установите CLI-команду `code` (в VS Code: `Shift+Ctrl+P` → `Install 'code' command in PATH`).
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Лицензия
|
|
225
|
+
|
|
226
|
+
MIT License. См. файл [LICENSE](LICENSE).
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Вклад
|
|
231
|
+
|
|
232
|
+
PR и issues приветствуются!
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gpu-server-setup
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Один клик — удалённый GPU-сервер с Ollama + VS Code Remote
|
|
5
|
+
Author: The Fool
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: typer[all]>=0.12
|
|
11
|
+
Requires-Dist: rich>=13.7
|
|
12
|
+
Requires-Dist: paramiko>=3.4
|
|
13
|
+
Requires-Dist: pydantic-settings>=2.5
|
|
14
|
+
Requires-Dist: tomli>=2.0; python_version >= "3.9"
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
```markdown
|
|
18
|
+
# Gpu-setup — один клик до удалённого GPU-сервера с Ollama и VS Code Remote
|
|
19
|
+
|
|
20
|
+
Автоматическая настройка SSH, установка [Ollama](https://ollama.com/), загрузка LLM-моделей и открытие VS Code Remote на удалённом GPU-сервере — всё одной командой.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Возможности
|
|
25
|
+
|
|
26
|
+
- **Генерация SSH-ключей** (ed25519 / RSA) и автоматическое копирование публичного ключа на сервер.
|
|
27
|
+
- **Настройка `~/.ssh/config`** с алиасом для удобного подключения.
|
|
28
|
+
- **Установка Ollama** (через официальный скрипт) с запуском сервиса.
|
|
29
|
+
- **Загрузка моделей** (например, `llama3.2`, `gemma2`) с живым выводом прогресса.
|
|
30
|
+
- **Автоматический запуск VS Code Remote** (если установлен `code` CLI).
|
|
31
|
+
- **Режим `--dry-run`** для безопасного просмотра плана действий.
|
|
32
|
+
- **Красивый вывод** с помощью `rich`.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Предварительные требования
|
|
37
|
+
|
|
38
|
+
- Python 3.8+ (рекомендуется 3.10)
|
|
39
|
+
- Удалённый сервер с Linux (Ubuntu/Debian/CentOS) и предустановленным Python
|
|
40
|
+
- Доступ по SSH с паролем (для первого копирования ключа)
|
|
41
|
+
- На локальной машине:
|
|
42
|
+
- `ssh-keygen` (обычно уже есть)
|
|
43
|
+
- `ssh` клиент
|
|
44
|
+
- (опционально) `code` для VS Code Remote (если планируете открывать редактор)
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Установка
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Клонируем репозиторий
|
|
52
|
+
git clone https://github.com/your-username/gpu-setup.git
|
|
53
|
+
cd gpu-setup
|
|
54
|
+
|
|
55
|
+
# Устанавливаем в виртуальное окружение (рекомендуется)
|
|
56
|
+
python -m venv venv
|
|
57
|
+
source venv/bin/activate # или venv\Scripts\activate на Windows
|
|
58
|
+
|
|
59
|
+
# Устанавливаем зависимости
|
|
60
|
+
pip install -r requirements.txt
|
|
61
|
+
|
|
62
|
+
# Устанавливаем пакет в режиме разработки (опционально)
|
|
63
|
+
pip install -e .
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
После этого команда `gpu-setup` будет доступна в терминале.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Использование
|
|
71
|
+
|
|
72
|
+
Общий синтаксис:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
gpu-setup [команда] [опции]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Доступные команды
|
|
79
|
+
|
|
80
|
+
| Команда | Описание |
|
|
81
|
+
|-------------------|----------|
|
|
82
|
+
| `setup` | **Полная настройка:** SSH, Ollama, модель, VS Code |
|
|
83
|
+
| `ssh` | Только настройка SSH-ключей и алиаса |
|
|
84
|
+
| `ollama-install` | Установка Ollama на сервер |
|
|
85
|
+
| `model-pull` | Загрузка указанной модели (после установки Ollama) |
|
|
86
|
+
| `vscode` | Открытие VS Code Remote (если `code` установлен) |
|
|
87
|
+
| `plan` | Показать план действий без выполнения (dry-run) |
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Подробное описание команд
|
|
92
|
+
|
|
93
|
+
### `gpu-setup setup`
|
|
94
|
+
|
|
95
|
+
Выполняет все шаги: генерация ключа, копирование, настройка SSH-конфига, установка Ollama, загрузка модели, открытие VS Code.
|
|
96
|
+
|
|
97
|
+
**Опции:**
|
|
98
|
+
|
|
99
|
+
- `--ip` (обязательный) — IP сервера
|
|
100
|
+
- `--user` (обязательный) — имя пользователя
|
|
101
|
+
- `--password` (обязательный для первого подключения) — пароль (можно ввести интерактивно)
|
|
102
|
+
- `--model` — модель для скачивания (например `llama3.2`)
|
|
103
|
+
- `--install-ollama` / `--no-install` — устанавливать Ollama (по умолчанию `--install-ollama`)
|
|
104
|
+
- `--no-vscode` — не открывать VS Code (по умолчанию открывает)
|
|
105
|
+
- `--dry-run` — только показать план
|
|
106
|
+
- `--key-type` — тип ключа: `ed25519` (по умолчанию) или `rsa`
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
### `gpu-setup ssh`
|
|
111
|
+
|
|
112
|
+
Только настройка SSH-доступа по ключу.
|
|
113
|
+
|
|
114
|
+
**Опции:**
|
|
115
|
+
|
|
116
|
+
- `--ip` (обязательный)
|
|
117
|
+
- `--user` (обязательный)
|
|
118
|
+
- `--password` (обязательный для первого копирования)
|
|
119
|
+
- `--dry-run`
|
|
120
|
+
- `--key-type`
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### `gpu-setup ollama-install`
|
|
125
|
+
|
|
126
|
+
Устанавливает Ollama на сервер (требуется пароль для sudo).
|
|
127
|
+
|
|
128
|
+
**Опции:**
|
|
129
|
+
|
|
130
|
+
- `--ip` (обязательный)
|
|
131
|
+
- `--user` (обязательный)
|
|
132
|
+
- `--password` (обязательный, т.к. нужен sudo)
|
|
133
|
+
- `--dry-run`
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
### `gpu-setup model-pull MODEL`
|
|
138
|
+
|
|
139
|
+
Скачивает указанную модель через Ollama. Если сервис Ollama не запущен, пытается запустить его автоматически.
|
|
140
|
+
|
|
141
|
+
**Аргументы:**
|
|
142
|
+
|
|
143
|
+
- `MODEL` — имя модели (например `llama3.2`)
|
|
144
|
+
|
|
145
|
+
**Опции:**
|
|
146
|
+
|
|
147
|
+
- `--ip` (обязательный)
|
|
148
|
+
- `--user` (обязательный)
|
|
149
|
+
- `--password` (необязателен, если SSH уже настроен; нужен только для запуска сервиса)
|
|
150
|
+
- `--dry-run`
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
### `gpu-setup vscode`
|
|
155
|
+
|
|
156
|
+
Открывает текущую сессию в VS Code Remote.
|
|
157
|
+
|
|
158
|
+
**Опции:**
|
|
159
|
+
|
|
160
|
+
- `--ip` (обязательный)
|
|
161
|
+
- `--user` (обязательный)
|
|
162
|
+
- `--password` (не требуется, если ключ настроен)
|
|
163
|
+
- `--dry-run`
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
### `gpu-setup plan`
|
|
168
|
+
|
|
169
|
+
Показывает, какие шаги будут выполнены при вызове `setup` с указанными параметрами.
|
|
170
|
+
|
|
171
|
+
**Опции:**
|
|
172
|
+
|
|
173
|
+
- `--ip` (обязательный)
|
|
174
|
+
- `--user` (обязательный)
|
|
175
|
+
- `--model`
|
|
176
|
+
- `--install-ollama` / `--no-install`
|
|
177
|
+
- `--no-vscode`
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Примеры использования
|
|
182
|
+
|
|
183
|
+
### 1. Полная настройка (самый частый вариант)
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
gpu-setup setup --ip 195.208.16.2 --user user --password sWDoAVym --model llama3.2
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### 2. Только SSH-ключи + config
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
gpu-setup ssh --ip 195.208.16.2 --user user --password sWDoAVym
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### 3. Только установить Ollama
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
gpu-setup ollama-install --ip 195.208.16.2 --user user --password sWDoAVym
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### 4. Только скачать модель (пароль уже не нужен, если SSH настроен)
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
gpu-setup model-pull llama3.2 --ip 195.208.16.2 --user user
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### 5. Только открыть VS Code Remote
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
gpu-setup vscode --ip 195.208.16.2 --user user
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### 6. Посмотреть план перед запуском
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
gpu-setup plan --ip 195.208.16.2 --user user --model llama3.2
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Dry-run
|
|
222
|
+
|
|
223
|
+
Любая команда поддерживает флаг `--dry-run`.
|
|
224
|
+
В этом режиме ничего не изменяется, а только выводится, что было бы сделано.
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
gpu-setup setup --ip 195.208.16.2 --user user --dry-run
|
|
228
|
+
```
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Устранение неполадок
|
|
232
|
+
|
|
233
|
+
- **`ssh: Could not resolve hostname`** — проверьте IP и доступность сервера.
|
|
234
|
+
- **Ошибка при копировании ключа** — убедитесь, что пароль верный и на сервере разрешена аутентификация по паролю (`PasswordAuthentication yes` в `/etc/ssh/sshd_config`).
|
|
235
|
+
- **Ollama не устанавливается** — возможно, требуется вручную разрешить `sudo` без пароля или передать пароль в опции.
|
|
236
|
+
- **VS Code не открывается** — установите CLI-команду `code` (в VS Code: `Shift+Ctrl+P` → `Install 'code' command in PATH`).
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Лицензия
|
|
241
|
+
|
|
242
|
+
MIT License. См. файл [LICENSE](LICENSE).
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Вклад
|
|
247
|
+
|
|
248
|
+
PR и issues приветствуются!
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
gpu_server_setup.egg-info/PKG-INFO
|
|
5
|
+
gpu_server_setup.egg-info/SOURCES.txt
|
|
6
|
+
gpu_server_setup.egg-info/dependency_links.txt
|
|
7
|
+
gpu_server_setup.egg-info/entry_points.txt
|
|
8
|
+
gpu_server_setup.egg-info/requires.txt
|
|
9
|
+
gpu_server_setup.egg-info/top_level.txt
|
|
10
|
+
gpu_setup/__init__.py
|
|
11
|
+
gpu_setup/__version__.py
|
|
12
|
+
gpu_setup/cli.py
|
|
13
|
+
gpu_setup/config.py
|
|
14
|
+
gpu_setup/core.py
|
|
15
|
+
gpu_setup/rich_console.py
|
|
16
|
+
gpu_setup/utils.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gpu_setup
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.5.0"
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from .core import GPUServer, get_plan
|
|
5
|
+
from .rich_console import header, plan_table, success, error
|
|
6
|
+
|
|
7
|
+
app = typer.Typer(
|
|
8
|
+
name="gpu-setup",
|
|
9
|
+
help="Один клик — удалённый GPU-сервер с Ollama + VS Code Remote",
|
|
10
|
+
rich_markup_mode="rich",
|
|
11
|
+
add_completion=True,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@app.command()
|
|
16
|
+
def plan(
|
|
17
|
+
ip: str = typer.Option(..., "--ip", help="IP-адрес сервера"),
|
|
18
|
+
user: str = typer.Option(..., "--user", help="Имя пользователя"),
|
|
19
|
+
model: Optional[str] = typer.Option(None, "--model", help="Модель Ollama"),
|
|
20
|
+
install_ollama: bool = typer.Option(True, "--install-ollama/--no-install"),
|
|
21
|
+
no_vscode: bool = typer.Option(False, "--no-vscode"),
|
|
22
|
+
):
|
|
23
|
+
"""Показать план выполнения (dry-run)"""
|
|
24
|
+
server = GPUServer(ip=ip, user=user, dry_run=True)
|
|
25
|
+
plan_list = get_plan(server, install_ollama, model, no_vscode)
|
|
26
|
+
header(f"План для сервера {ip}")
|
|
27
|
+
plan_table(plan_list)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@app.command()
|
|
31
|
+
def setup(
|
|
32
|
+
ip: str = typer.Option(..., "--ip", help="IP-адрес сервера"),
|
|
33
|
+
user: str = typer.Option(..., "--user", help="Имя пользователя"),
|
|
34
|
+
password: str = typer.Option(
|
|
35
|
+
None,
|
|
36
|
+
"--password",
|
|
37
|
+
prompt=True,
|
|
38
|
+
hide_input=True,
|
|
39
|
+
help="Пароль (только для первого подключения и sudo)",
|
|
40
|
+
),
|
|
41
|
+
model: Optional[str] = typer.Option(None, "--model", help="Модель Ollama (например llama3.2)"),
|
|
42
|
+
install_ollama: bool = typer.Option(True, "--install-ollama/--no-install"),
|
|
43
|
+
no_vscode: bool = typer.Option(False, "--no-vscode"),
|
|
44
|
+
dry_run: bool = typer.Option(False, "--dry-run"),
|
|
45
|
+
key_type: str = typer.Option("ed25519", "--key-type"),
|
|
46
|
+
):
|
|
47
|
+
if dry_run:
|
|
48
|
+
plan(ip=ip, user=user, model=model, install_ollama=install_ollama, no_vscode=no_vscode)
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
server = GPUServer(
|
|
52
|
+
ip=ip, user=user, password=password, key_type=key_type, dry_run=False
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
server.generate_key()
|
|
56
|
+
server.copy_pubkey()
|
|
57
|
+
server.setup_ssh_config()
|
|
58
|
+
server.test_connection()
|
|
59
|
+
|
|
60
|
+
if install_ollama:
|
|
61
|
+
server.install_ollama()
|
|
62
|
+
|
|
63
|
+
if model:
|
|
64
|
+
server.pull_model(model)
|
|
65
|
+
|
|
66
|
+
if not no_vscode:
|
|
67
|
+
server.open_vscode()
|
|
68
|
+
|
|
69
|
+
success("Готово! 🎉")
|
|
70
|
+
if model:
|
|
71
|
+
typer.echo(f"\nЗапуск модели: [bold]ollama run {model}[/bold]")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@app.command()
|
|
76
|
+
def ssh(
|
|
77
|
+
ip: str = typer.Option(..., "--ip", help="IP-адрес сервера"),
|
|
78
|
+
user: str = typer.Option(..., "--user", help="Имя пользователя"),
|
|
79
|
+
password: str = typer.Option(
|
|
80
|
+
None,
|
|
81
|
+
"--password",
|
|
82
|
+
prompt=True,
|
|
83
|
+
hide_input=True,
|
|
84
|
+
help="Пароль для первого копирования ключа",
|
|
85
|
+
),
|
|
86
|
+
dry_run: bool = typer.Option(False, "--dry-run"),
|
|
87
|
+
key_type: str = typer.Option("ed25519", "--key-type"),
|
|
88
|
+
):
|
|
89
|
+
server = GPUServer(ip=ip, user=user, password=password, key_type=key_type, dry_run=dry_run)
|
|
90
|
+
server.generate_key()
|
|
91
|
+
server.copy_pubkey()
|
|
92
|
+
server.setup_ssh_config()
|
|
93
|
+
server.test_connection()
|
|
94
|
+
success("SSH полностью настроен!")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@app.command("ollama-install")
|
|
98
|
+
def ollama_install(
|
|
99
|
+
ip: str = typer.Option(..., "--ip"),
|
|
100
|
+
user: str = typer.Option(..., "--user"),
|
|
101
|
+
password: str = typer.Option(
|
|
102
|
+
None,
|
|
103
|
+
"--password",
|
|
104
|
+
prompt=True,
|
|
105
|
+
hide_input=True,
|
|
106
|
+
help="Пароль для sudo",
|
|
107
|
+
),
|
|
108
|
+
dry_run: bool = typer.Option(False, "--dry-run"),
|
|
109
|
+
):
|
|
110
|
+
server = GPUServer(ip=ip, user=user, password=password, dry_run=dry_run)
|
|
111
|
+
server.install_ollama()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@app.command("model-pull")
|
|
115
|
+
def model_pull(
|
|
116
|
+
model: str = typer.Argument(..., help="Название модели (llama3.2, gemma2 и т.д.)"),
|
|
117
|
+
ip: str = typer.Option(..., "--ip"),
|
|
118
|
+
user: str = typer.Option(..., "--user"),
|
|
119
|
+
password: Optional[str] = typer.Option(None, "--password", hide_input=True),
|
|
120
|
+
dry_run: bool = typer.Option(False, "--dry-run"),
|
|
121
|
+
):
|
|
122
|
+
server = GPUServer(ip=ip, user=user, password=password, dry_run=dry_run)
|
|
123
|
+
server.pull_model(model)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@app.command()
|
|
127
|
+
def vscode(
|
|
128
|
+
ip: str = typer.Option(..., "--ip"),
|
|
129
|
+
user: str = typer.Option(..., "--user"),
|
|
130
|
+
password: Optional[str] = typer.Option(None, "--password", hide_input=True),
|
|
131
|
+
dry_run: bool = typer.Option(False, "--dry-run"),
|
|
132
|
+
):
|
|
133
|
+
server = GPUServer(ip=ip, user=user, password=password, dry_run=dry_run)
|
|
134
|
+
server.open_vscode()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
if __name__ == "__main__":
|
|
138
|
+
app()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Settings(BaseSettings):
|
|
6
|
+
model_config = SettingsConfigDict(
|
|
7
|
+
env_prefix="GPU_SETUP_",
|
|
8
|
+
env_file=".env",
|
|
9
|
+
env_file_encoding="utf-8",
|
|
10
|
+
extra="ignore",
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
default_key_type: str = "ed25519"
|
|
14
|
+
config_dir: Path = Path.home() / ".config" / "gpu-setup"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
settings = Settings()
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import sys
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import paramiko
|
|
6
|
+
from paramiko import SSHClient, AutoAddPolicy, SSHException
|
|
7
|
+
|
|
8
|
+
from .utils import run_local, console
|
|
9
|
+
from .rich_console import header, success, error, warning
|
|
10
|
+
from .config import settings
|
|
11
|
+
|
|
12
|
+
import subprocess
|
|
13
|
+
import shutil
|
|
14
|
+
import time
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class GPUServer:
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
ip: str,
|
|
21
|
+
user: str,
|
|
22
|
+
password: Optional[str] = None,
|
|
23
|
+
key_type: str = "ed25519",
|
|
24
|
+
dry_run: bool = False,
|
|
25
|
+
):
|
|
26
|
+
self.ip = ip
|
|
27
|
+
self.user = user
|
|
28
|
+
self.password = password
|
|
29
|
+
self.dry_run = dry_run
|
|
30
|
+
self.key_type = key_type
|
|
31
|
+
self.alias = f"gpu-server-{ip.replace('.', '-')}"
|
|
32
|
+
self.key_path = Path.home() / f".ssh/id_{key_type}_gpu"
|
|
33
|
+
self.pub_path = self.key_path.with_suffix(".pub")
|
|
34
|
+
self.config_file = Path.home() / ".ssh/config"
|
|
35
|
+
|
|
36
|
+
def generate_key(self) -> None:
|
|
37
|
+
header("Генерация SSH-ключа")
|
|
38
|
+
if self.key_path.exists():
|
|
39
|
+
success("SSH-ключ уже существует")
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
if self.dry_run:
|
|
43
|
+
console.print("[dim]DRY-RUN: ssh-keygen[/dim]")
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
cmd = (
|
|
47
|
+
["ssh-keygen", "-t", self.key_type, "-f", str(self.key_path), "-N", "", "-q"]
|
|
48
|
+
if self.key_type == "ed25519"
|
|
49
|
+
else ["ssh-keygen", "-t", "rsa", "-b", "4096", "-f", str(self.key_path), "-N", "", "-q"]
|
|
50
|
+
)
|
|
51
|
+
run_local(cmd)
|
|
52
|
+
success(f"Ключ {self.key_type} создан")
|
|
53
|
+
|
|
54
|
+
def copy_pubkey(self) -> None:
|
|
55
|
+
header("Копирование публичного ключа на сервер")
|
|
56
|
+
if self.dry_run:
|
|
57
|
+
console.print("[dim]DRY-RUN: копирование ключа через paramiko[/dim]")
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
if not self.password:
|
|
61
|
+
error("Для первого подключения нужен пароль")
|
|
62
|
+
sys.exit(1)
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
client = SSHClient()
|
|
66
|
+
client.set_missing_host_key_policy(AutoAddPolicy())
|
|
67
|
+
client.connect(
|
|
68
|
+
hostname=self.ip,
|
|
69
|
+
username=self.user,
|
|
70
|
+
password=self.password,
|
|
71
|
+
timeout=15,
|
|
72
|
+
look_for_keys=False,
|
|
73
|
+
allow_agent=False,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
pubkey = self.pub_path.read_text().strip()
|
|
77
|
+
cmd = (
|
|
78
|
+
f"mkdir -p ~/.ssh && chmod 700 ~/.ssh && "
|
|
79
|
+
f"echo '{pubkey}' >> ~/.ssh/authorized_keys && "
|
|
80
|
+
f"chmod 600 ~/.ssh/authorized_keys"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
_, stdout, stderr = client.exec_command(cmd)
|
|
84
|
+
stdout.read()
|
|
85
|
+
stderr.read()
|
|
86
|
+
client.close()
|
|
87
|
+
success("Публичный ключ добавлен")
|
|
88
|
+
except SSHException as e:
|
|
89
|
+
error(f"Не удалось скопировать ключ: {e}")
|
|
90
|
+
sys.exit(1)
|
|
91
|
+
|
|
92
|
+
def setup_ssh_config(self) -> None:
|
|
93
|
+
header("Настройка ~/.ssh/config")
|
|
94
|
+
if self.dry_run:
|
|
95
|
+
console.print("[dim]DRY-RUN: запись алиаса[/dim]")
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
entry = f"""
|
|
99
|
+
Host {self.alias}
|
|
100
|
+
HostName {self.ip}
|
|
101
|
+
User {self.user}
|
|
102
|
+
IdentityFile {self.key_path}
|
|
103
|
+
StrictHostKeyChecking no
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
self.config_file.parent.mkdir(exist_ok=True)
|
|
107
|
+
self.config_file.touch(exist_ok=True)
|
|
108
|
+
|
|
109
|
+
content = self.config_file.read_text()
|
|
110
|
+
if f"Host {self.alias}" not in content:
|
|
111
|
+
with open(self.config_file, "a", encoding="utf-8") as f:
|
|
112
|
+
f.write(entry)
|
|
113
|
+
success(f"Алиас {self.alias} добавлен")
|
|
114
|
+
else:
|
|
115
|
+
success("Алиас уже существует")
|
|
116
|
+
|
|
117
|
+
def test_connection(self) -> None:
|
|
118
|
+
header("Тест SSH-подключения по ключу")
|
|
119
|
+
if self.dry_run:
|
|
120
|
+
console.print("[dim]DRY-RUN: тест SSH[/dim]")
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
client = SSHClient()
|
|
125
|
+
client.set_missing_host_key_policy(AutoAddPolicy())
|
|
126
|
+
client.connect(
|
|
127
|
+
hostname=self.ip,
|
|
128
|
+
username=self.user,
|
|
129
|
+
key_filename=str(self.key_path),
|
|
130
|
+
timeout=10,
|
|
131
|
+
look_for_keys=False,
|
|
132
|
+
allow_agent=False,
|
|
133
|
+
)
|
|
134
|
+
client.exec_command("exit")
|
|
135
|
+
client.close()
|
|
136
|
+
success("SSH по ключу работает")
|
|
137
|
+
except Exception as e:
|
|
138
|
+
error(f"Тест SSH провалился: {e}")
|
|
139
|
+
sys.exit(1)
|
|
140
|
+
|
|
141
|
+
def install_ollama(self) -> None:
|
|
142
|
+
header("Установка Ollama")
|
|
143
|
+
if self.dry_run:
|
|
144
|
+
console.print("[dim]DRY-RUN: установка Ollama[/dim]")
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
client = SSHClient()
|
|
148
|
+
client.set_missing_host_key_policy(AutoAddPolicy())
|
|
149
|
+
client.connect(hostname=self.ip, username=self.user, key_filename=str(self.key_path))
|
|
150
|
+
|
|
151
|
+
_, stdout, _ = client.exec_command("command -v ollama")
|
|
152
|
+
if stdout.read().decode().strip():
|
|
153
|
+
success("Ollama уже установлен")
|
|
154
|
+
client.close()
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
if not self.password:
|
|
158
|
+
error("Для установки Ollama нужен пароль (sudo)")
|
|
159
|
+
sys.exit(1)
|
|
160
|
+
|
|
161
|
+
sudo_client = SSHClient()
|
|
162
|
+
sudo_client.set_missing_host_key_policy(AutoAddPolicy())
|
|
163
|
+
sudo_client.connect(
|
|
164
|
+
hostname=self.ip,
|
|
165
|
+
username=self.user,
|
|
166
|
+
password=self.password,
|
|
167
|
+
timeout=15,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
install_cmd = "bash -c 'curl -fsSL https://ollama.com/install.sh | sh'"
|
|
171
|
+
_, stdout, stderr = sudo_client.exec_command(f"sudo -S {install_cmd}")
|
|
172
|
+
stdout.channel.send(self.password + "\n")
|
|
173
|
+
stdout.channel.flush()
|
|
174
|
+
|
|
175
|
+
output = stdout.read().decode()
|
|
176
|
+
err_output = stderr.read().decode()
|
|
177
|
+
if err_output:
|
|
178
|
+
console.print(err_output, end="")
|
|
179
|
+
|
|
180
|
+
console.print("[bold blue]Запускаем Ollama сервис...[/bold blue]")
|
|
181
|
+
start_cmd = "sudo systemctl enable ollama && sudo systemctl start ollama"
|
|
182
|
+
_, stdout, stderr = sudo_client.exec_command(start_cmd)
|
|
183
|
+
|
|
184
|
+
stdout.read()
|
|
185
|
+
err_output = stderr.read().decode()
|
|
186
|
+
|
|
187
|
+
if err_output:
|
|
188
|
+
console.print(err_output, end="")
|
|
189
|
+
|
|
190
|
+
success("Ollama сервис запущен")
|
|
191
|
+
|
|
192
|
+
sudo_client.close()
|
|
193
|
+
client.close()
|
|
194
|
+
success("Ollama успешно установлен")
|
|
195
|
+
|
|
196
|
+
def pull_model(self, model: str) -> None:
|
|
197
|
+
header(f"Скачивание модели {model}")
|
|
198
|
+
if self.dry_run:
|
|
199
|
+
console.print(f"[dim]DRY-RUN: ollama pull {model}[/dim]")
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
client = SSHClient()
|
|
203
|
+
client.set_missing_host_key_policy(AutoAddPolicy())
|
|
204
|
+
client.connect(hostname=self.ip, username=self.user, key_filename=str(self.key_path))
|
|
205
|
+
|
|
206
|
+
_, stdout, _ = client.exec_command("command -v ollama")
|
|
207
|
+
if not stdout.read().decode().strip():
|
|
208
|
+
error("Ollama не установлен. Добавь --install-ollama")
|
|
209
|
+
sys.exit(1)
|
|
210
|
+
|
|
211
|
+
def check_ollama_service():
|
|
212
|
+
_, stdout, stderr = client.exec_command("ollama list")
|
|
213
|
+
exit_code = stdout.channel.recv_exit_status()
|
|
214
|
+
if exit_code != 0:
|
|
215
|
+
err_output = stderr.read().decode()
|
|
216
|
+
if "could not connect to ollama server" in err_output:
|
|
217
|
+
return False, err_output
|
|
218
|
+
return True, None
|
|
219
|
+
|
|
220
|
+
service_ok, err_msg = check_ollama_service()
|
|
221
|
+
if not service_ok:
|
|
222
|
+
warning("Ollama сервис не отвечает. Пытаемся запустить...")
|
|
223
|
+
|
|
224
|
+
if self.password:
|
|
225
|
+
sudo_client = SSHClient()
|
|
226
|
+
sudo_client.set_missing_host_key_policy(AutoAddPolicy())
|
|
227
|
+
sudo_client.connect(
|
|
228
|
+
hostname=self.ip,
|
|
229
|
+
username=self.user,
|
|
230
|
+
password=self.password,
|
|
231
|
+
timeout=15,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
console.print("[dim]Запуск через systemctl...[/dim]")
|
|
235
|
+
stdin, stdout, stderr = sudo_client.exec_command("sudo -S systemctl start ollama")
|
|
236
|
+
stdin.write(self.password + "\n")
|
|
237
|
+
stdin.flush()
|
|
238
|
+
stdout.read()
|
|
239
|
+
stderr.read()
|
|
240
|
+
sudo_client.close()
|
|
241
|
+
|
|
242
|
+
console.print("[dim]Даём 5 секунд на запуск...[/dim]")
|
|
243
|
+
time.sleep(5)
|
|
244
|
+
|
|
245
|
+
service_ok, err_msg = check_ollama_service()
|
|
246
|
+
if not service_ok:
|
|
247
|
+
warning(f"systemctl не помог: {err_msg}")
|
|
248
|
+
console.print("[dim]Пробуем запустить ollama serve в фоне...[/dim]")
|
|
249
|
+
sudo_client2 = SSHClient()
|
|
250
|
+
sudo_client2.set_missing_host_key_policy(AutoAddPolicy())
|
|
251
|
+
sudo_client2.connect(
|
|
252
|
+
hostname=self.ip,
|
|
253
|
+
username=self.user,
|
|
254
|
+
password=self.password,
|
|
255
|
+
timeout=15,
|
|
256
|
+
)
|
|
257
|
+
start_cmd = f"nohup ollama serve > /tmp/ollama.log 2>&1 &"
|
|
258
|
+
stdin, stdout, stderr = sudo_client2.exec_command(f"sudo -S -u {self.user} bash -c '{start_cmd}'")
|
|
259
|
+
stdin.write(self.password + "\n")
|
|
260
|
+
stdin.flush()
|
|
261
|
+
stdout.read()
|
|
262
|
+
stderr.read()
|
|
263
|
+
sudo_client2.close()
|
|
264
|
+
|
|
265
|
+
console.print("[dim]Даём 10 секунд на запуск...[/dim]")
|
|
266
|
+
time.sleep(10)
|
|
267
|
+
|
|
268
|
+
# Последняя проверка
|
|
269
|
+
service_ok, err_msg = check_ollama_service()
|
|
270
|
+
if not service_ok:
|
|
271
|
+
error(f"Не удалось запустить сервис: {err_msg}")
|
|
272
|
+
sys.exit(1)
|
|
273
|
+
else:
|
|
274
|
+
success("Сервис Ollama успешно запущен (через nohup)")
|
|
275
|
+
else:
|
|
276
|
+
success("Сервис Ollama успешно запущен (через systemctl)")
|
|
277
|
+
else:
|
|
278
|
+
error("Пароль не задан, невозможно автоматически запустить сервис.")
|
|
279
|
+
console.print("Запустите вручную: ssh {} 'sudo systemctl start ollama' или 'ollama serve'".format(self.alias))
|
|
280
|
+
sys.exit(1)
|
|
281
|
+
|
|
282
|
+
_, stdout, _ = client.exec_command(f"ollama list | grep -q '{model}'")
|
|
283
|
+
if stdout.channel.recv_exit_status() == 0:
|
|
284
|
+
success(f"Модель {model} уже есть")
|
|
285
|
+
client.close()
|
|
286
|
+
return
|
|
287
|
+
|
|
288
|
+
console.print(f"[bold blue]Скачиваем {model}...[/bold blue]")
|
|
289
|
+
stdin, stdout, stderr = client.exec_command(f"ollama pull {model}", get_pty=True)
|
|
290
|
+
channel = stdout.channel
|
|
291
|
+
|
|
292
|
+
while not channel.exit_status_ready():
|
|
293
|
+
if channel.recv_ready():
|
|
294
|
+
print(channel.recv(1024).decode("utf-8", errors="replace"), end="", flush=True)
|
|
295
|
+
if channel.recv_stderr_ready():
|
|
296
|
+
print(channel.recv_stderr(1024).decode("utf-8", errors="replace"), end="", flush=True)
|
|
297
|
+
|
|
298
|
+
print(stdout.read().decode(), end="")
|
|
299
|
+
err_out = stderr.read().decode()
|
|
300
|
+
if err_out:
|
|
301
|
+
print(err_out, end="")
|
|
302
|
+
client.close()
|
|
303
|
+
success(f"Модель {model} готова")
|
|
304
|
+
|
|
305
|
+
def open_vscode(self) -> None:
|
|
306
|
+
header("Открываем VS Code Remote")
|
|
307
|
+
if self.dry_run:
|
|
308
|
+
console.print(f"[dim]DRY-RUN: code --remote ssh-remote+{self.alias}[/dim]")
|
|
309
|
+
return
|
|
310
|
+
|
|
311
|
+
if shutil.which("code"):
|
|
312
|
+
subprocess.run(["code", "--remote", f"ssh-remote+{self.alias}"])
|
|
313
|
+
success("VS Code Remote открыт")
|
|
314
|
+
else:
|
|
315
|
+
warning("Команда 'code' не найдена. Установи VS Code CLI")
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def get_plan(server: GPUServer, install_ollama: bool, model: Optional[str], no_vscode: bool) -> list:
|
|
319
|
+
plan = [
|
|
320
|
+
("Генерация ключа", "ed25519 (если нет)"),
|
|
321
|
+
("Копирование ключа", "на сервер"),
|
|
322
|
+
("SSH config", f"алиас {server.alias}"),
|
|
323
|
+
("Тест подключения", "по ключу"),
|
|
324
|
+
]
|
|
325
|
+
if install_ollama:
|
|
326
|
+
plan.append(("Ollama", "установка"))
|
|
327
|
+
if model:
|
|
328
|
+
plan.append(("Модель", f"ollama pull {model}"))
|
|
329
|
+
if not no_vscode:
|
|
330
|
+
plan.append(("VS Code", "открытие Remote"))
|
|
331
|
+
return plan
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from rich.console import Console
|
|
2
|
+
from rich.panel import Panel
|
|
3
|
+
from rich.table import Table
|
|
4
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
5
|
+
|
|
6
|
+
console = Console()
|
|
7
|
+
|
|
8
|
+
def header(text: str) -> None:
|
|
9
|
+
console.print(Panel(f"[bold green]{text}[/bold green]", expand=False))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def success(text: str) -> None:
|
|
13
|
+
console.print(f"[bold green]{text}[/bold green]")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def warning(text: str) -> None:
|
|
17
|
+
console.print(f"[bold yellow]{text}[/bold yellow]")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def error(text: str) -> None:
|
|
21
|
+
console.print(f"[bold red]{text}[/bold red]")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def plan_table(plan: list[tuple[str, str]]) -> None:
|
|
25
|
+
table = Table(title="План выполнения", show_header=True, header_style="bold magenta")
|
|
26
|
+
table.add_column("Шаг", style="cyan")
|
|
27
|
+
table.add_column("Описание", style="dim")
|
|
28
|
+
for step, desc in plan:
|
|
29
|
+
table.add_row(step, desc)
|
|
30
|
+
console.print(table)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def live_ollama_output() -> None:
|
|
34
|
+
with Progress(
|
|
35
|
+
SpinnerColumn(),
|
|
36
|
+
TextColumn("[bold blue]Скачиваем модель...[/bold blue]"),
|
|
37
|
+
transient=True,
|
|
38
|
+
) as progress:
|
|
39
|
+
progress.add_task("pull", total=None)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
console = Console()
|
|
9
|
+
|
|
10
|
+
def run_local(cmd: List[str], dry_run: bool = False) -> None:
|
|
11
|
+
if dry_run:
|
|
12
|
+
console.print(f"[dim]DRY-RUN → {' '.join(cmd)}[/dim]")
|
|
13
|
+
return
|
|
14
|
+
try:
|
|
15
|
+
subprocess.run(cmd, check=True, capture_output=True)
|
|
16
|
+
except subprocess.CalledProcessError as e:
|
|
17
|
+
console.print(f"[red]Локальная команда провалилась:[/red] {e}")
|
|
18
|
+
sys.exit(1)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def ensure_dir(path: Path) -> None:
|
|
22
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "gpu-server-setup"
|
|
3
|
+
version = "0.4.0"
|
|
4
|
+
description = "Один клик — удалённый GPU-сервер с Ollama + VS Code Remote"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = {text = "MIT"}
|
|
7
|
+
authors = [{name = "The Fool"}]
|
|
8
|
+
requires-python = ">=3.9"
|
|
9
|
+
dependencies = [
|
|
10
|
+
"typer[all]>=0.12",
|
|
11
|
+
"rich>=13.7",
|
|
12
|
+
"paramiko>=3.4",
|
|
13
|
+
"pydantic-settings>=2.5",
|
|
14
|
+
"tomli>=2.0; python_version >= '3.9'",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.scripts]
|
|
18
|
+
gpu-server-setup = "gpu_setup.cli:app"
|