keysloth 0.1.1

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.
data/lib/keysloth.rb ADDED
@@ -0,0 +1,275 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'keysloth/version'
4
+ require_relative 'keysloth/crypto'
5
+ require_relative 'keysloth/git_manager'
6
+ require_relative 'keysloth/file_manager'
7
+ require_relative 'keysloth/config'
8
+ require_relative 'keysloth/logger'
9
+ require_relative 'keysloth/errors'
10
+
11
+ # Основной модуль KeySloth - Ruby gem для управления зашифрованными секретами
12
+ #
13
+ # KeySloth предоставляет инструменты для безопасного хранения и управления
14
+ # секретами (сертификаты, ключи, конфигурационные файлы) в зашифрованном виде
15
+ # в Git репозиториях.
16
+ #
17
+ # @example Базовое использование
18
+ # # Получение секретов из репозитория
19
+ # KeySloth.pull(repo_url: 'git@github.com:company/secrets.git',
20
+ # branch: 'main',
21
+ # password: 'secret_password',
22
+ # local_path: './secrets')
23
+ #
24
+ # # Отправка секретов в репозиторий
25
+ # KeySloth.push(repo_url: 'git@github.com:company/secrets.git',
26
+ # branch: 'main',
27
+ # password: 'secret_password',
28
+ # local_path: './secrets')
29
+ #
30
+ # @example Использование с конфигурационным файлом
31
+ # # Создайте файл .keyslothrc:
32
+ # # repo_url: "git@github.com:company/secrets.git"
33
+ # # branch: "main"
34
+ # # local_path: "./secrets"
35
+ #
36
+ # # Теперь достаточно указать только пароль:
37
+ # KeySloth.pull(password: 'secret_password', config_file: '.keyslothrc')
38
+ #
39
+ # @example Обработка ошибок
40
+ # begin
41
+ # KeySloth.pull(repo_url: repo_url, password: password, local_path: './secrets')
42
+ # rescue KeySloth::CryptoError => e
43
+ # puts "Ошибка расшифровки: #{e.message}"
44
+ # rescue KeySloth::RepositoryError => e
45
+ # puts "Ошибка репозитория: #{e.message}"
46
+ # rescue KeySloth::FileSystemError => e
47
+ # puts "Ошибка файловой системы: #{e.message}"
48
+ # end
49
+ #
50
+ # @example Использование в CI/CD с переменными окружения
51
+ # # Установите переменные окружения:
52
+ # # export SSH_PRIVATE_KEY="$(cat ~/.ssh/id_rsa)"
53
+ # # export SECRET_PASSWORD="your_password"
54
+ # # export GIT_AUTHOR_NAME="CI Bot"
55
+ # # export GIT_AUTHOR_EMAIL="ci@company.com"
56
+ #
57
+ # KeySloth.pull(
58
+ # repo_url: 'git@github.com:company/secrets.git',
59
+ # password: ENV['SECRET_PASSWORD'],
60
+ # local_path: './secrets'
61
+ # )
62
+ #
63
+ # @author KeySloth Team
64
+ # @since 0.1.0
65
+ module KeySloth
66
+ class << self
67
+ # Получает и расшифровывает секреты из удаленного Git репозитория
68
+ #
69
+ # @param repo_url [String] URL Git репозитория (SSH)
70
+ # @param branch [String] Ветка для получения секретов (по умолчанию 'main')
71
+ # @param password [String] Пароль для расшифровки секретов
72
+ # @param local_path [String] Локальный путь для сохранения расшифрованных секретов
73
+ # @param config_file [String, nil] Путь к файлу конфигурации (опционально)
74
+ # @return [Boolean] true при успешном выполнении
75
+ # @raise [KeySloth::RepositoryError] при ошибках работы с репозиторием
76
+ # @raise [KeySloth::CryptoError] при ошибках расшифровки
77
+ # @raise [KeySloth::FileSystemError] при ошибках файловой системы
78
+ def pull(repo_url:, password:, local_path:, branch: 'main', config_file: nil)
79
+ start_time = Time.now
80
+ logger = Logger.new
81
+ config = Config.load(config_file)
82
+
83
+ # Аудит логирование начала операции
84
+ logger.audit('pull_start', {
85
+ repo_url: repo_url,
86
+ branch: branch,
87
+ local_path: local_path,
88
+ config_file: config_file
89
+ })
90
+
91
+ # Объединяем параметры с конфигурацией (параметры имеют приоритет, nil не перетирает)
92
+ merged_config = config.merge({
93
+ repo_url: repo_url,
94
+ branch: branch,
95
+ local_path: local_path
96
+ }.compact)
97
+
98
+ logger.info("Начинаем получение секретов из репозитория: #{repo_url}")
99
+
100
+ git_manager = GitManager.new(merged_config[:repo_url], logger)
101
+ file_manager = FileManager.new(logger)
102
+ crypto = Crypto.new(password, logger)
103
+
104
+ # Создаем backup перед операцией
105
+ if File.exist?(merged_config[:local_path])
106
+ file_manager.create_backup(merged_config[:local_path])
107
+ end
108
+
109
+ # Клонируем/обновляем репозиторий и получаем зашифрованные файлы
110
+ encrypted_files = git_manager.pull_encrypted_files(merged_config[:branch])
111
+
112
+ # Создаем локальную директорию если не существует
113
+ file_manager.ensure_directory(merged_config[:local_path])
114
+
115
+ # Расшифровываем и сохраняем файлы с проверкой целостности
116
+ integrity_failures = []
117
+
118
+ encrypted_files.each do |encrypted_file|
119
+ original_filename = encrypted_file[:name].gsub(/\.enc$/, '')
120
+
121
+ # Проверяем целостность зашифрованного файла
122
+ integrity_check = crypto.verify_integrity_detailed(encrypted_file[:content])
123
+
124
+ unless integrity_check[:valid]
125
+ error_msg = "Ошибка целостности для #{original_filename}: #{integrity_check[:error] || 'структура данных повреждена'}"
126
+ logger.error(error_msg)
127
+ integrity_failures << { file: original_filename, error: integrity_check[:error] }
128
+ next
129
+ end
130
+
131
+ logger.debug("Проверка целостности пройдена для: #{original_filename}")
132
+
133
+ # Расшифровываем файл
134
+ decrypted_content = crypto.decrypt_file(encrypted_file[:content])
135
+ local_file_path = File.join(merged_config[:local_path], original_filename)
136
+
137
+ # Проверяем целостность расшифрованного файла
138
+ if decrypted_content.nil? || decrypted_content.empty?
139
+ logger.warn("Расшифрованный файл пустой: #{original_filename}")
140
+ end
141
+
142
+ file_manager.write_file(local_file_path, decrypted_content)
143
+ logger.info("Расшифрован файл: #{original_filename} (размер: #{decrypted_content.length} байт)")
144
+ end
145
+
146
+ # Проверяем наличие ошибок целостности
147
+ unless integrity_failures.empty?
148
+ failure_details = integrity_failures.map { |f| "#{f[:file]}: #{f[:error]}" }.join('; ')
149
+ raise CryptoError, "Обнаружены ошибки целостности файлов: #{failure_details}"
150
+ end
151
+
152
+ duration = Time.now - start_time
153
+ logger.info("Успешно получено и расшифровано #{encrypted_files.size} файлов")
154
+
155
+ # Аудит логирование успешного завершения
156
+ logger.security_log('pull', :success, duration: duration, details: {
157
+ files_count: encrypted_files.size,
158
+ repo_url: repo_url,
159
+ branch: merged_config[:branch]
160
+ })
161
+
162
+ true
163
+ rescue StandardError => e
164
+ duration = Time.now - start_time
165
+ logger.security_log('pull', :failure, duration: duration, details: {
166
+ error: e.class.name,
167
+ repo_url: repo_url,
168
+ branch: branch
169
+ })
170
+ raise
171
+ ensure
172
+ git_manager&.cleanup
173
+ end
174
+
175
+ # Шифрует и отправляет секреты в удаленный Git репозиторий
176
+ #
177
+ # @param repo_url [String] URL Git репозитория (SSH)
178
+ # @param branch [String] Ветка для отправки секретов (по умолчанию 'main')
179
+ # @param password [String] Пароль для шифрования секретов
180
+ # @param local_path [String] Локальный путь с секретами для шифрования
181
+ # @param config_file [String, nil] Путь к файлу конфигурации (опционально)
182
+ # @param commit_message [String] Сообщение коммита (опционально)
183
+ # @return [Boolean] true при успешном выполнении
184
+ # @raise [KeySloth::RepositoryError] при ошибках работы с репозиторием
185
+ # @raise [KeySloth::CryptoError] при ошибках шифрования
186
+ # @raise [KeySloth::FileSystemError] при ошибках файловой системы
187
+ def push(repo_url:, password:, local_path:, branch: 'main',
188
+ config_file: nil, commit_message: nil)
189
+ start_time = Time.now
190
+ logger = Logger.new
191
+ config = Config.load(config_file)
192
+
193
+ # Аудит логирование начала операции
194
+ logger.audit('push_start', {
195
+ repo_url: repo_url,
196
+ branch: branch,
197
+ local_path: local_path,
198
+ config_file: config_file,
199
+ commit_message: commit_message
200
+ })
201
+
202
+ # Объединяем параметры с конфигурацией (параметры имеют приоритет, nil не перетирает)
203
+ merged_config = config.merge({
204
+ repo_url: repo_url,
205
+ branch: branch,
206
+ local_path: local_path
207
+ }.compact)
208
+
209
+ logger.info("Начинаем отправку секретов в репозиторий: #{repo_url}")
210
+
211
+ git_manager = GitManager.new(merged_config[:repo_url], logger)
212
+ file_manager = FileManager.new(logger)
213
+ crypto = Crypto.new(password, logger)
214
+
215
+ # Проверяем существование локальной директории
216
+ unless file_manager.directory_exists?(merged_config[:local_path])
217
+ raise FileSystemError, "Локальная директория не существует: #{merged_config[:local_path]}"
218
+ end
219
+
220
+ # Получаем список файлов для шифрования
221
+ local_files = file_manager.collect_secret_files(merged_config[:local_path])
222
+
223
+ if local_files.empty?
224
+ logger.warn('Не найдено файлов секретов для отправки')
225
+ return true
226
+ end
227
+
228
+ # Клонируем репозиторий и переключаемся на нужную ветку
229
+ git_manager.prepare_repository(merged_config[:branch])
230
+
231
+ # Шифруем и подготавливаем файлы
232
+ encrypted_files = local_files.map do |file_path|
233
+ content = file_manager.read_file(file_path)
234
+ encrypted_content = crypto.encrypt_file(content)
235
+ relative_path = file_manager.get_relative_path(file_path, merged_config[:local_path])
236
+ encrypted_filename = "#{relative_path}.enc"
237
+
238
+ {
239
+ path: encrypted_filename,
240
+ content: encrypted_content
241
+ }
242
+ end
243
+
244
+ # Записываем зашифрованные файлы в репозиторий
245
+ git_manager.write_encrypted_files(encrypted_files)
246
+
247
+ # Создаем коммит и отправляем
248
+ commit_msg = commit_message || "Update secrets: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
249
+ git_manager.commit_and_push(commit_msg, merged_config[:branch])
250
+
251
+ duration = Time.now - start_time
252
+ logger.info("Успешно зашифровано и отправлено #{encrypted_files.size} файлов")
253
+
254
+ # Аудит логирование успешного завершения
255
+ logger.security_log('push', :success, duration: duration, details: {
256
+ files_count: encrypted_files.size,
257
+ repo_url: repo_url,
258
+ branch: merged_config[:branch],
259
+ commit_message: commit_msg
260
+ })
261
+
262
+ true
263
+ rescue StandardError => e
264
+ duration = Time.now - start_time
265
+ logger.security_log('push', :failure, duration: duration, details: {
266
+ error: e.class.name,
267
+ repo_url: repo_url,
268
+ branch: branch
269
+ })
270
+ raise
271
+ ensure
272
+ git_manager&.cleanup
273
+ end
274
+ end
275
+ end
data/task/cr.md ADDED
@@ -0,0 +1,59 @@
1
+ # Change Request: Поддержка SSH ключей id_ed25519 и улучшение UX
2
+
3
+ ## Контекст проблемы
4
+ При запуске команды (например, `keysloth pull`) возникла ошибка:
5
+
6
+ ```
7
+ Ошибка выполнения операции: SSH ключ не найден: /Users/<user>/.ssh/id_rsa
8
+ ```
9
+
10
+ Причина: в `KeySloth::GitManager#create_ssh_credentials` по умолчанию ожидаются ключи формата RSA (`~/.ssh/id_rsa` и `~/.ssh/id_rsa.pub`). На машине пользователя используются ключи Ed25519 (`id_ed25519`, `id_ed25519.pub`).
11
+
12
+ ## Временные решения (workarounds)
13
+ 1. Использовать ключи из переменных окружения (подходит для локали и CI/CD):
14
+ ```bash
15
+ export SSH_PRIVATE_KEY="$(cat ~/.ssh/id_ed25519)"
16
+ export SSH_PUBLIC_KEY="$(cat ~/.ssh/id_ed25519.pub)"
17
+ keysloth pull -r git@github.com:USER/REPO.git -p "PASSWORD"
18
+ ```
19
+
20
+ 2. Создать симлинки под ожидаемые имена (быстро, локально):
21
+ ```bash
22
+ ln -s ~/.ssh/id_ed25519 ~/.ssh/id_rsa
23
+ ln -s ~/.ssh/id_ed25519.pub ~/.ssh/id_rsa.pub
24
+ ```
25
+
26
+ 3. Сгенерировать отдельный RSA‑ключ (если принципиально):
27
+ ```bash
28
+ ssh-keygen -t rsa -b 4096 -C "you@example.com" -f ~/.ssh/id_rsa
29
+ # добавить ~/.ssh/id_rsa.pub в GitHub/GitLab
30
+ ```
31
+
32
+ ## Предложения по доработке
33
+ 1. Поддержать Ed25519 «из коробки» в `create_ssh_credentials`:
34
+ - Приоритет: переменные окружения `SSH_PRIVATE_KEY`/`SSH_PUBLIC_KEY` (как сейчас).
35
+ - Fallback: искать пары ключей по стандартным путям в порядке:
36
+ - `~/.ssh/id_rsa(.pub)`
37
+ - `~/.ssh/id_ed25519(.pub)`
38
+ - Если пара найдена — использовать; иначе выбрасывать `AuthenticationError` с подсказками.
39
+
40
+ 2. Добавить конфигурируемый путь к ключу:
41
+ - Env: `KEYSLOTH_SSH_KEY_PATH` (если задан — использовать `<path>` и `<path>.pub`).
42
+ - Опция CLI: `--ssh-key PATH` (пробрасывать в `GitManager`).
43
+
44
+ 3. Рассмотреть поддержку ssh-agent:
45
+ - Использовать `Rugged::Credentials::SshKeyFromAgent` при отсутствии файлов и env.
46
+
47
+ 4. Улучшить сообщение об ошибке:
48
+ - Печатать проверённые пути, быстрые шаги: env, симлинк, `--ssh-key`.
49
+
50
+ ## Тестирование
51
+ - Добавить тесты в `spec/keysloth/git_manager_spec.rb`:
52
+ - только `id_rsa`
53
+ - только `id_ed25519`
54
+ - ключи через ENV
55
+ - путь через `KEYSLOTH_SSH_KEY_PATH`
56
+ - отсутствие ключей → `AuthenticationError`
57
+
58
+ ## Документация
59
+ - Обновить README (раздел «Настройка SSH ключей») и `task/test_plan.md` (раздел про SSH) с примерами для Ed25519 и `--ssh-key`/env.
data/task/plan.md ADDED
@@ -0,0 +1,169 @@
1
+ # План реализации KeySloth - Ruby Gem для управления зашифрованными секретами
2
+
3
+ ## 1. Подготовка проекта
4
+
5
+ ### 1.1 Структура gem'а
6
+ - [x] Создать основную структуру ruby gem'а
7
+ - [x] Настроить gemspec файл с описанием и зависимостями
8
+ - [x] Создать базовую структуру директорий (lib/, bin/, spec/, etc.)
9
+ - [x] Настроить Gemfile с необходимыми зависимостями
10
+
11
+ ### 1.2 Инфраструктура
12
+ - [x] Настроить RSpec для тестирования
13
+ - [x] Настроить RuboCop для линтинга кода
14
+ - [x] Создать базовый Rakefile
15
+ - [ ] Настроить CI/CD (GitHub Actions)
16
+
17
+ ## 2. Основная функциональность
18
+
19
+ ### 2.1 Криптографический модуль
20
+ - [x] Реализовать модуль шифрования/дешифрования файлов
21
+ - [x] Выбрать алгоритм шифрования (AES-256-GCM)
22
+ - [x] Реализовать генерацию ключей из паролей (PBKDF2)
23
+ - [x] Добавить обработку различных типов файлов (.cer, .p12, .mobileprovisioning, .json)
24
+
25
+ ### 2.2 Работа с Git репозиториями
26
+ - [x] Реализовать клонирование удаленного репозитория (через системные команды)
27
+ - [x] Реализовать переключение на нужную ветку
28
+ - [x] Реализовать получение файлов из репозитория
29
+ - [x] Реализовать коммит и push изменений обратно в репозиторий
30
+ - [x] Добавить очистку временных файлов после работы
31
+
32
+ ### 2.3 Управление файловой системой
33
+ - [x] Реализовать создание локальной директории для секретов
34
+ - [x] Реализовать извлечение зашифрованных файлов в локальную директорию
35
+ - [x] Реализовать упаковку измененных файлов для отправки в репозиторий
36
+ - [x] Добавить валидацию путей и проверку прав доступа
37
+
38
+ ## 3. Интерфейс командной строки
39
+
40
+ ### 3.1 Команда получения секретов
41
+ - [x] Реализовать команду `pull` для получения и дешифровки секретов
42
+ - [x] Параметры: URL репозитория, ветка, пароль, путь к локальной директории
43
+ - [x] Добавить валидацию входных параметров
44
+ - [x] Реализовать подробное логирование процесса
45
+ - [x] Добавить обработку ошибок (неверный пароль, недоступный репозиторий, etc.)
46
+
47
+ ### 3.2 Команда отправки секретов
48
+ - [x] Реализовать команду `push` для шифровки и отправки секретов
49
+ - [x] Те же параметры что и для pull
50
+ - [x] Добавить проверку изменений перед отправкой
51
+ - [x] Реализовать создание коммита с описанием изменений
52
+ - [x] Добавить подтверждение действия перед отправкой
53
+
54
+ ### 3.3 Help команда
55
+ - [x] Реализовать команду `help` с описанием всех команд
56
+ - [x] Добавить примеры использования
57
+ - [x] Добавить описание параметров и опций
58
+ - [x] Создать man-страницу или подробную документацию
59
+
60
+ ### 3.4 Дополнительные команды
61
+ - [x] Реализовать команду `init` для первичной настройки
62
+ - [x] Добавить команду `status` для проверки состояния локальных секретов
63
+ - [x] Реализовать команду `validate` для проверки целостности файлов
64
+ - [x] Добавить команду `restore` для восстановления из backup'а
65
+ - [x] Добавить команду `version` для отображения версии
66
+
67
+ ## 4. Безопасность и обработка ошибок
68
+
69
+ ### 4.1 Безопасность
70
+ - [x] Добавить проверку целостности зашифрованных файлов
71
+
72
+ ### 4.2 Обработка ошибок
73
+ - [x] Добавить логирование ошибок и отладочной информации
74
+
75
+ ## 5. Тестирование
76
+
77
+ ### 5.1 Unit тесты
78
+ - [x] Написать тесты для криптографического модуля
79
+ - [x] Написать тесты для работы с файловой системой
80
+ - [x] Написать тесты для Git операций
81
+ - [x] Написать тесты для CLI интерфейса
82
+
83
+ ## 6. Документация и комментарии
84
+
85
+ ### 6.1 Код документация
86
+ - [x] Добавить подробные комментарии ко всем публичным методам
87
+ - [x] Создать YARD документацию
88
+ - [x] Добавить примеры использования в комментариях
89
+ - [x] Документировать все исключения и edge cases
90
+
91
+ ### 6.2 Пользовательская документация
92
+ - [x] Создать README.md с инструкциями по установке и использованию
93
+ - [x] Написать CHANGELOG.md
94
+ - [x] Создать примеры использования в Makefile
95
+ - [x] Добавить troubleshooting guide
96
+
97
+ ## 7. Публикация и развертывание
98
+
99
+ ### 7.1 Подготовка к публикации
100
+ - [x] Финальная проверка всех тестов
101
+ - [x] Код-ревью и рефакторинг
102
+ - [x] Проверка безопасности и соответствия best practices
103
+ - [x] Финализация версии и тегирование
104
+
105
+ ### 7.2 Публикация
106
+ - [ ] Публикация gem'а в RubyGems.org
107
+ - [ ] Создание GitHub release с описанием изменений
108
+ - [ ] Обновление документации на публичных ресурсах
109
+ - [ ] Создание примеров интеграции с популярными CI/CD системами
110
+
111
+ ## 8. Поддержка и развитие
112
+
113
+ ### 8.1 Мониторинг
114
+ - [ ] Настроить мониторинг использования gem'а
115
+ - [ ] Создать систему сбора обратной связи
116
+ - [ ] Настроить автоматические уведомления о проблемах
117
+
118
+ ### 8.2 Развитие
119
+ - [ ] Планирование будущих фич (GUI интерфейс, интеграции)
120
+ - [ ] Поддержка обратной совместимости
121
+ - [ ] Регулярные обновления зависимостей
122
+ - [ ] Поддержка сообщества пользователей
123
+
124
+
125
+
126
+
127
+
128
+ ## Вопросы для уточнения
129
+
130
+ ### 9.1 Технические детали
131
+ - [x] Какой алгоритм шифрования использовать? (AES-256-GCM, ChaCha20-Poly1305, другой?)
132
+ **Ответ:** AES-256-GCM - наиболее подходящий выбор, так как обеспечивает защиту целостности данных и аутентификацию, широко поддерживается, имеет хорошую производительность и является стандартом индустрии.
133
+
134
+ - [x] В каком формате хранить зашифрованные файлы в репозитории? (один архив или отдельные файлы?)
135
+ **Ответ:** Отдельные зашифрованные файлы с сохранением исходных имен + расширение .enc. Это обеспечивает гранулярность изменений в Git, позволяет отслеживать изменения конкретных файлов и упрощает парциальное обновление секретов.
136
+
137
+ - [x] Как именно будет называться gem? (KeySloth или другое имя?)
138
+ **Ответ:** KeySloth - подходящее название, отражает суть инструмента (ключи + медленная и осторожная работа с секретами). Альтернатива: SecretSloth.
139
+
140
+ ### 9.2 Git и аутентификация
141
+ - [x] Поддерживать только HTTPS или также SSH ключи для доступа к репозиторию?
142
+ **Ответ:** Только SSH ключи. Это обеспечивает более высокий уровень безопасности и исключает необходимость передачи паролей/токенов. В CI/CD системах также можно настроить SSH ключи через deploy keys или service accounts.
143
+
144
+ - [x] Как передавать учетные данные Git? (параметры команды, переменные окружения, git credential helper?)
145
+ **Ответ:** Использование SSH ключей. Для локальной работы - стандартные SSH ключи из ~/.ssh/. Для CI/CD - настройка SSH ключей через переменные окружения (SSH_PRIVATE_KEY) или секреты системы CI/CD. Автоматическое добавление хостов в known_hosts при необходимости.
146
+
147
+ ### 9.3 CLI интерфейс
148
+ - [x] Нужен ли namespace для команд? (например `keysloth pull` vs отдельные исполняемые файлы?)
149
+ **Ответ:** Да, использовать namespace `keysloth <command>`. Это современный подход, обеспечивает чистоту пространства имен системы и упрощает добавление новых команд. Основные команды: `keysloth pull`, `keysloth push`, `keysloth help`.
150
+
151
+ - [x] Нужна ли поддержка конфигурационных файлов для хранения URL репозитория и других параметров?
152
+ **Ответ:** Да, опциональная поддержка `.keyslothrc` (YAML формат) для хранения URL репозитория, ветки по умолчанию, путей. Параметры командной строки должны иметь приоритет над конфигом для гибкости в CI/CD.
153
+
154
+ ### 9.4 Обработка конфликтов
155
+ - [x] Как обрабатывать ситуацию, когда несколько разработчиков одновременно изменяют секреты?
156
+ **Ответ:** Использовать стратегию "fail-fast": перед push выполнять pull и проверять изменения. При обнаружении конфликтов - прекращать операцию с подробным сообщением об ошибке и рекомендациями по разрешению. Добавить команду `keysloth status` для проверки состояния.
157
+
158
+ - [x] Как разрешать конфликты при слиянии изменений в Git?
159
+ **Ответ:** Не автоматически. Инструмент должен обнаруживать конфликты и требовать ручного разрешения администратором проекта. Предоставлять детальную информацию о конфликтующих файлах и временных метках изменений.
160
+
161
+ ### 9.5 Дополнительные требования
162
+ - [x] Какие версии Ruby должны поддерживаться? (2.7+, 3.0+ и т.д.)
163
+ **Ответ:** Ruby 2.7+ для совместимости с современными CI/CD системами и не слишком устаревшими проектами. Активно тестировать на Ruby 2.7, 3.0, 3.1, 3.2. Указать minimum_ruby_version = "2.7.0" в gemspec.
164
+
165
+ - [x] Нужны ли подробные логи работы? В какой формат? (stdout, файлы?)
166
+ **Ответ:** Да, многоуровневое логирование. По умолчанию INFO в stdout. Параметр `--verbose` для DEBUG уровня. Параметр `--quiet` для только ERROR. Логи в простом текстовом формате с timestamp для удобства отладки в CI/CD.
167
+
168
+ - [x] Нужна ли возможность создания резервных копий перед изменениями?
169
+ **Ответ:** Да, автоматическое создание backup'ов локальной директории с секретами перед выполнением pull операций. Хранить последние 3 копии с временными метками. Команда `keysloth restore` для восстановления из backup'а.
@@ -0,0 +1,103 @@
1
+ # План отказа от Rugged/libgit2 и переход на системный git
2
+
3
+ ## Текущая проблема
4
+ - Использование `Rugged` (libgit2) приводит к частым проблемам:
5
+ - Нужна сборка нативного гема: `cmake`, `libgit2`, `libssh2`, `openssl`, корректный `PKG_CONFIG_PATH`.
6
+ - Неполная сборка без SSH → ошибка `unsupported URL protocol` при `git@...`.
7
+ - Предзагрузка `RUBYOPT=-r rugged` ломает другие ruby-команды до установки гема (`gem install` и т.п.).
8
+ - Для пользователей и CI это поднимает порог входа и вызывает нестабильность.
9
+
10
+ Вывод: переходим на системный `git` (через `Open3`), убираем зависимость от `rugged`.
11
+
12
+ ## Цели
13
+ - Исключить `rugged`/`libgit2` и любые шаги по их установке.
14
+ - Сохранить поведение `pull`/`push`/ветки/коммиты/cleanup.
15
+ - Улучшить DX: всё работает там, где есть стандартный `git` и SSH.
16
+
17
+ ## Таск‑лист миграции
18
+ 1) Подготовка
19
+ - Убедиться, что `keysloth.gemspec` не тянет `rugged` (оставляем как есть, rugged не в зависимостях).
20
+ - Создать ветку `feat/system-git`.
21
+
22
+ 2) Реализация системного Git в `KeySloth::GitManager`
23
+ - Заменить логику на вызовы системного `git`:
24
+ - clone: `git clone --depth 1 <repo_url> <tmp>` (если ветка не `main`, затем checkout).
25
+ - checkout: `git checkout <branch>` или `git checkout -b <branch> --track origin/<branch>`.
26
+ - ensure up-to-date: `git fetch origin <branch>` + сравнить `git rev-parse HEAD` и `git rev-parse origin/<branch>`; при расхождении — `git pull --ff-only` или ошибка.
27
+ - list .enc: `Dir.glob("**/*.enc", base: tmp)` + `File.read`.
28
+ - clear .enc: `Dir.glob("**/*.enc", base: tmp)` + `File.delete`.
29
+ - write .enc: создать директории, `File.write`.
30
+ - add & commit: `git add -A`, `git commit -m "msg"` (автор из ENV: `GIT_AUTHOR_*`/`GIT_COMMITTER_*` или `-c user.name=... -c user.email=...`).
31
+ - push: `git push origin <branch>`.
32
+ - Инкапсулировать выполнение команд в метод: `run_git(cmd, env: {})` с возвратом `[stdout, stderr, status]`, логированием и конвертацией ошибок в `RepositoryError`.
33
+ - Сохранить `cleanup` (удаление временной директории).
34
+
35
+ 3) Аутентификация SSH
36
+ - По умолчанию использовать стандартный SSH (ssh-agent, `~/.ssh/config`).
37
+ - Если заданы `SSH_PRIVATE_KEY`/`SSH_PUBLIC_KEY` (CI):
38
+ - создать временную директорию ключей, записать файлы, `chmod 600`.
39
+ - выставить `GIT_SSH_COMMAND="ssh -i <path> -o StrictHostKeyChecking=no"` для всех git‑команд.
40
+ - Опционально поддержать `KEYSLOTH_SSH_KEY_PATH` (если указан — использовать его в `GIT_SSH_COMMAND`).
41
+ - Позже: можно добавить поддержку `id_ed25519` в явном виде, но системный `ssh` уже это умеет.
42
+
43
+ 4) Обработка ошибок и логирование
44
+ - Для каждой git‑команды логировать: краткое описание, команду, код выхода; при ошибке показывать stderr.
45
+ - Генерировать `RepositoryError` с понятным сообщением и советом (проверить доступ/ветку/SSH).
46
+
47
+ 5) Тесты
48
+ - Переписать `spec/keysloth/git_manager_spec.rb`:
49
+ - мокать `Open3.capture3` и проверять последовательность вызовов.
50
+ - кейсы: clone/checkout/fetch/pull ok, нет ветки, нет изменений для коммита, запись файлов, push ok, ошибка SSH/доступа, ошибка git.
51
+ - кейсы с `GIT_SSH_COMMAND` и env‑ключами.
52
+
53
+ 6) Документация и примеры
54
+ - Обновить `task/test_plan.md`:
55
+ - удалить блок «Важно про Rugged» и шаги `gem install rugged`, `RUBYOPT=-r rugged`.
56
+ - уточнить, что требуется установленный `git` и корректный SSH.
57
+ - поправить пример для push: использовать `git@github.com:...`, а не `ssh@github.com:...`.
58
+ - Обновить `README.md`:
59
+ - удалить упоминания rugged и предзагрузки.
60
+ - добавить раздел «Зависимости»: нужен только `git`.
61
+ - в CI‑примере убрать установку rugged и шаг с RUBYOPT, при необходимости показать `GIT_SSH_COMMAND`.
62
+ - Добавить запись в `CHANGELOG.md` (0.1.x → 0.1.(x+1)): «Переведён на системный git; удалены инструкции про rugged».
63
+
64
+ 7) Релиз
65
+ - Прогнать тесты и линтинг.
66
+ - Обновить версию (`lib/keysloth/version.rb`).
67
+ - Сформировать gem, smoke‑тест: `pull/push` на чистой машине с только `git`.
68
+ - Создать GitHub release.
69
+
70
+ ## Критерии готовности
71
+ - `keysloth pull/push` работают без установки rugged/libgit2.
72
+ - Все тесты зелёные; покрыт основной happy‑path и ошибки SSH.
73
+ - `README.md`/`task/test_plan.md` не содержат упоминаний rugged, примеры актуальны.
74
+ - Ошибки `git` отображаются с понятными причинами и советами.
75
+
76
+ ## Согласованные решения
77
+ 1) Поведение pull
78
+ - Выполняем `git fetch origin <branch>` и затем `git pull --ff-only`. При невозможности fast-forward — завершаем с ошибкой с понятным сообщением.
79
+
80
+ 2) Автор коммита
81
+ - Требуются глобальные настройки Git: `user.name` и `user.email`. Перед коммитом проверяем их наличие, при отсутствии — ошибка с подсказкой настроить `git config --global`.
82
+
83
+ 3) Приоритет и способ SSH-аутентификации
84
+ - Приоритет источников ключей: `KEYSLOTH_SSH_KEY_PATH` → `SSH_PRIVATE_KEY`/`SSH_PUBLIC_KEY` (создаём временные файлы, `chmod 600`) → системные ключи/агент (`~/.ssh`, `ssh-agent`).
85
+ - При использовании нестандартных ключей выставляем `GIT_SSH_COMMAND` с вызовом системного `ssh`.
86
+
87
+ 4) Passphrase-ключи
88
+ - Поддержка passphrase-ключей не входит в объём. Возможна работа через `ssh-agent` или безфразные ключи.
89
+
90
+ 5) Стратегия клонирования
91
+ - По умолчанию используем `git clone --depth 1` (shallow). Если для `pull --ff-only` потребуется история, выполняем однократно `git fetch --unshallow` и повторяем `pull`.
92
+ - Предусматриваем переключатель окружения `KEYSLOTH_FULL_CLONE=true` для полного клона при необходимости.
93
+
94
+ 6) Очистка `.enc`
95
+ - Очищаем глобально все `**/*.enc` в рабочем дереве перед записью новых файлов. Риски (удаление «чужих» `.enc`, гонки) осознанно принимаются; перед очисткой выполняем `fetch` + `pull --ff-only` для минимизации расхождений.
96
+
97
+ 7) Зависимости и SSH
98
+ - Используются стандартные системные инструменты: Git CLI и системный SSH (обычно OpenSSH). Дополнительные нативные библиотеки не требуются.
99
+ - В CI при использовании ключей из ENV формируем `GIT_SSH_COMMAND`, например: `ssh -i <path> -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null`.
100
+ - В локальной разработке проверку хостов не отключаем.
101
+
102
+ 8) Версионирование
103
+ - Увеличиваем patch-версию.