keysloth 0.1.1 → 0.2.0

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.
@@ -25,8 +25,6 @@ module KeySloth
25
25
  # @author KeySloth Team
26
26
  # @since 0.1.0
27
27
  class FileManager
28
- # Поддерживаемые расширения файлов секретов
29
- SECRET_FILE_EXTENSIONS = %w[.cer .p12 .mobileprovisioning .json].freeze
30
28
 
31
29
  # Максимальное количество backup'ов
32
30
  DEFAULT_BACKUP_COUNT = 3
@@ -75,7 +73,7 @@ module KeySloth
75
73
  @logger.debug("Читаем файл: #{file_path}")
76
74
 
77
75
  begin
78
- content = File.read(file_path)
76
+ content = File.binread(file_path)
79
77
  @logger.debug("Файл прочитан успешно (размер: #{content.length} байт)")
80
78
  content
81
79
  rescue StandardError => e
@@ -97,7 +95,7 @@ module KeySloth
97
95
  directory = File.dirname(file_path)
98
96
  ensure_directory(directory) unless directory_exists?(directory)
99
97
 
100
- File.write(file_path, content)
98
+ File.binwrite(file_path, content)
101
99
  @logger.debug("Файл записан успешно (размер: #{content.length} байт)")
102
100
  rescue StandardError => e
103
101
  @logger.error('Ошибка записи файла', e)
@@ -105,7 +103,13 @@ module KeySloth
105
103
  end
106
104
  end
107
105
 
108
- # Собирает все файлы секретов из директории
106
+ # Собирает все файлы секретов из директории (wildcard)
107
+ #
108
+ # Рекурсивно собирает любые обычные файлы, исключая:
109
+ # - .enc файлы (они являются артефактами репозитория)
110
+ # - содержимое .git директорий
111
+ # - общеизвестные мусорные файлы (.DS_Store, Thumbs.db)
112
+ # - локальный README.md внутри каталога секретов
109
113
  #
110
114
  # @param directory_path [String] Путь к директории с секретами
111
115
  # @return [Array<String>] Массив путей к файлам секретов
@@ -116,11 +120,24 @@ module KeySloth
116
120
  begin
117
121
  validate_directory_access!(directory_path)
118
122
 
119
- files = []
120
- SECRET_FILE_EXTENSIONS.each do |extension|
121
- pattern = File.join(directory_path, "**/*#{extension}")
122
- matching_files = Dir.glob(pattern)
123
- files.concat(matching_files)
123
+ all_candidates = Dir.glob(File.join(directory_path, '**', '*'), File::FNM_DOTMATCH)
124
+
125
+ files = all_candidates.select do |path|
126
+ next false unless File.file?(path)
127
+
128
+ relative = get_relative_path(path, directory_path)
129
+
130
+ # Исключаем .git содержимое
131
+ next false if relative.split(File::SEPARATOR).include?('.git')
132
+
133
+ # Исключаем артефакты шифрования
134
+ next false if File.extname(path).downcase == '.enc'
135
+
136
+ # Исключаем общеизвестный мусор и локальный README.md
137
+ base = File.basename(path)
138
+ next false if base == '.DS_Store' || base == 'Thumbs.db' || base == 'README.md'
139
+
140
+ true
124
141
  end
125
142
 
126
143
  @logger.info("Найдено #{files.size} файлов секретов")
@@ -147,6 +164,8 @@ module KeySloth
147
164
  # @raise [FileSystemError] при ошибках создания backup'а
148
165
  def create_backup(directory_path)
149
166
  return nil unless directory_exists?(directory_path)
167
+ # Отключение бэкапов: при нулевом или отрицательном лимите
168
+ return nil if @backup_count.to_i <= 0
150
169
 
151
170
  timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
152
171
  backup_name = "#{File.basename(directory_path)}_backup_#{timestamp}"
@@ -217,7 +236,7 @@ module KeySloth
217
236
 
218
237
  # Базовая проверка на читаемость
219
238
  begin
220
- content = File.read(file_path, 100) # Читаем первые 100 байт для проверки
239
+ content = File.binread(file_path, 100) # Читаем первые 100 байт для проверки
221
240
 
222
241
  # Дополнительная проверка по типу файла
223
242
  file_extension = File.extname(file_path).downcase
@@ -254,7 +273,7 @@ module KeySloth
254
273
  return result unless result[:non_empty]
255
274
 
256
275
  # Проверяем читаемость
257
- content = File.read(file_path, 200) # Читаем больше для детальной проверки
276
+ content = File.binread(file_path, 200) # Читаем больше для детальной проверки
258
277
  result[:readable] = true
259
278
 
260
279
  # Проверяем соответствие типу файла
@@ -322,6 +341,9 @@ module KeySloth
322
341
  # @param parent_dir [String] Родительская директория
323
342
  # @param base_name [String] Базовое имя директории
324
343
  def cleanup_old_backups(parent_dir, base_name)
344
+ # При отключённых бэкапах не удаляем существующие
345
+ return if @backup_count.to_i <= 0
346
+
325
347
  backups = list_backups(File.join(parent_dir, base_name))
326
348
 
327
349
  return if backups.size <= @backup_count
@@ -108,7 +108,7 @@ module KeySloth
108
108
  FileUtils.mkdir_p(File.dirname(file_path))
109
109
 
110
110
  # Записываем файл
111
- File.write(file_path, file_data[:content])
111
+ File.binwrite(file_path, file_data[:content])
112
112
  @logger.debug("Записан файл: #{file_data[:path]}")
113
113
  end
114
114
  rescue StandardError => e
@@ -271,7 +271,7 @@ module KeySloth
271
271
 
272
272
  files << {
273
273
  name: relative_path,
274
- content: File.read(full_path)
274
+ content: File.binread(full_path)
275
275
  }
276
276
  end
277
277
 
@@ -332,9 +332,9 @@ module KeySloth
332
332
  private_key_path = File.join(@ssh_tmp_dir, 'id_rsa')
333
333
  public_key_path = File.join(@ssh_tmp_dir, 'id_rsa.pub')
334
334
 
335
- File.write(private_key_path, ENV['SSH_PRIVATE_KEY'])
335
+ File.binwrite(private_key_path, ENV['SSH_PRIVATE_KEY'])
336
336
  File.chmod(0o600, private_key_path)
337
- File.write(public_key_path, ENV['SSH_PUBLIC_KEY']) if ENV['SSH_PUBLIC_KEY']
337
+ File.binwrite(public_key_path, ENV['SSH_PUBLIC_KEY']) if ENV['SSH_PUBLIC_KEY']
338
338
 
339
339
  # В CI отключаем проверку хостов (опционально)
340
340
  return %(ssh -i #{private_key_path} -o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null)
@@ -381,7 +381,33 @@ module KeySloth
381
381
  return [stdout, stderr] if allow_failure
382
382
 
383
383
  base_msg = (stderr.strip.empty? ? stdout.strip : stderr.strip)
384
- advice = 'Совет: проверьте доступ к репозиторию, корректность ветки и SSH-настройки.'
384
+
385
+ # Расширенные подсказки для типовых SSH/Git ошибок
386
+ extra_advice = nil
387
+ lower = base_msg.downcase
388
+ if lower.include?('permission denied (publickey)')
389
+ extra_advice = [
390
+ 'Совет: SSH-ключ не найден или не принят.',
391
+ '1) Проверьте ключи в агенте: ssh-add -l (или загрузите: ssh-add ~/.ssh/id_ed25519)',
392
+ '2) Укажите ключ явно: export KEYSLOTH_SSH_KEY_PATH=~/.ssh/id_ed25519 (или используйте SSH_PRIVATE_KEY в CI)',
393
+ '3) Проверьте права на ключ: chmod 600 ~/.ssh/<key>'
394
+ ].join("\n")
395
+ elsif lower.include?('host key verification failed')
396
+ extra_advice = [
397
+ 'Совет: Не пройдена проверка ключа хоста.',
398
+ '1) Добавьте хост в known_hosts: ssh-keyscan <host> >> ~/.ssh/known_hosts',
399
+ '2) Избегайте отключения StrictHostKeyChecking вне CI'
400
+ ].join("\n")
401
+ elsif lower.include?('repository not found') || lower.include?('could not read from remote repository')
402
+ extra_advice = [
403
+ 'Совет: Проверьте корректность URL и доступ к репозиторию.',
404
+ '1) Убедитесь, что у вас есть права доступа (приглашение/ключ).',
405
+ '2) Проверьте точность SSH URL: git@host:owner/repo.git'
406
+ ].join("\n")
407
+ end
408
+
409
+ generic_advice = 'Совет: проверьте доступ к репозиторию, корректность ветки и SSH-настройки.'
410
+ advice = extra_advice || generic_advice
385
411
  raise RepositoryError, [base_msg, advice].reject(&:empty?).join("\n")
386
412
  end
387
413
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  module KeySloth
4
4
  # Версия gem'а KeySloth
5
- VERSION = '0.1.1'
5
+ VERSION = '0.2.0'
6
6
  end
data/lib/keysloth.rb CHANGED
@@ -75,7 +75,7 @@ module KeySloth
75
75
  # @raise [KeySloth::RepositoryError] при ошибках работы с репозиторием
76
76
  # @raise [KeySloth::CryptoError] при ошибках расшифровки
77
77
  # @raise [KeySloth::FileSystemError] при ошибках файловой системы
78
- def pull(repo_url:, password:, local_path:, branch: 'main', config_file: nil)
78
+ def pull(repo_url:, password:, local_path: nil, branch: nil, config_file: nil)
79
79
  start_time = Time.now
80
80
  logger = Logger.new
81
81
  config = Config.load(config_file)
@@ -98,7 +98,14 @@ module KeySloth
98
98
  logger.info("Начинаем получение секретов из репозитория: #{repo_url}")
99
99
 
100
100
  git_manager = GitManager.new(merged_config[:repo_url], logger)
101
- file_manager = FileManager.new(logger)
101
+ # Прокидываем backup_count из конфигурации; невалидные значения заменяем дефолтом
102
+ configured_backup_count = merged_config[:backup_count]
103
+ backup_count = if configured_backup_count.is_a?(Integer) && configured_backup_count >= 0
104
+ configured_backup_count
105
+ else
106
+ KeySloth::FileManager::DEFAULT_BACKUP_COUNT
107
+ end
108
+ file_manager = FileManager.new(logger, backup_count)
102
109
  crypto = Crypto.new(password, logger)
103
110
 
104
111
  # Создаем backup перед операцией
@@ -184,7 +191,7 @@ module KeySloth
184
191
  # @raise [KeySloth::RepositoryError] при ошибках работы с репозиторием
185
192
  # @raise [KeySloth::CryptoError] при ошибках шифрования
186
193
  # @raise [KeySloth::FileSystemError] при ошибках файловой системы
187
- def push(repo_url:, password:, local_path:, branch: 'main',
194
+ def push(repo_url:, password:, local_path: nil, branch: nil,
188
195
  config_file: nil, commit_message: nil)
189
196
  start_time = Time.now
190
197
  logger = Logger.new
@@ -209,7 +216,13 @@ module KeySloth
209
216
  logger.info("Начинаем отправку секретов в репозиторий: #{repo_url}")
210
217
 
211
218
  git_manager = GitManager.new(merged_config[:repo_url], logger)
212
- file_manager = FileManager.new(logger)
219
+ configured_backup_count = merged_config[:backup_count]
220
+ backup_count = if configured_backup_count.is_a?(Integer) && configured_backup_count >= 0
221
+ configured_backup_count
222
+ else
223
+ KeySloth::FileManager::DEFAULT_BACKUP_COUNT
224
+ end
225
+ file_manager = FileManager.new(logger, backup_count)
213
226
  crypto = Crypto.new(password, logger)
214
227
 
215
228
  # Проверяем существование локальной директории
@@ -0,0 +1,16 @@
1
+ # Правила выполнения задачи
2
+
3
+ - Все новые фичи должны быть покрыты тестами
4
+ - По итогам выполнения задачи
5
+ - необходимо обязательно убедиться в успешном прохождении всех тестов, в случае ошибки - исправить код библиотеки или теста и запустить тесты заново, пока все они не будут заканчиваться успешно
6
+ - необходимо обязательно внести правки в файл CHANGELOG.md для версии, указанной в файле lib/keysloth/version.rb
7
+ - при необходимости - внести изменения в техническую документацию проекта
8
+
9
+ # Правила правки бага
10
+
11
+ - Каждый баг перед исправлением должен быть покрыт тестом, который должен фейлится до начала исправления бага
12
+ - Только после этого следует начинать правки бага
13
+ - После правок бага - необходимо убедиться, что весь набор тестов проекта проходит успешно, в случае ошибки - исправить код библиотеки или теста и запустить тесты заново, пока все они не будут заканчиваться успешно
14
+ - По итогам выполнения задачи
15
+ - необходимо обязательно внести правки в файл CHANGELOG.md для версии, указанной в файле lib/keysloth/version.rb
16
+ - при необходимости - внести изменения в техническую документацию проекта
@@ -0,0 +1,62 @@
1
+ # Wildcard extensions: поддержка любых типов файлов
2
+
3
+ ## Цель
4
+ Убрать ограничение на список поддерживаемых расширений. Инструмент должен шифровать/дешифровывать любые типы файлов в указанной директории, сохраняя текущую модель хранения `.enc` в репозитории.
5
+
6
+ ## Объём работ
7
+ - Сбор входных файлов: из whitelist расширений → на «все файлы», с разумными исключениями.
8
+ - Безопасная работа с бинарными файлами.
9
+ - Обновление CLI и документации.
10
+ - Актуализация тестов и тест-плана.
11
+
12
+ ## План задач
13
+ 1) Обновить сбор файлов в `FileManager#collect_secret_files`
14
+ - Рекурсивно собирать все обычные файлы (`File.file?`) в заданной директории.
15
+ - Исключить из выбора: `**/*.enc`, `**/.git/**`.
16
+ - Дополнительно исключить общеизвестные мусорные файлы по умолчанию: `.DS_Store`, `Thumbs.db`, локальный `README.md` в каталоге секретов.
17
+ - Подготовить точку расширения для будущей настройки include/exclude через конфиг (без реализации, если не требуется сейчас).
18
+
19
+ 2) Обеспечить бинарно-безопасный I/O
20
+ - Перейти на `File.binread`/`File.binwrite` в `FileManager` и местах использования (чтение/запись секретов).
21
+ - Проверить, что `Crypto` корректно работает с бинарными строками (подтвердить тестами).
22
+
23
+ 3) Проверки целостности файлов
24
+ - Сохранить специализированные проверки для известных типов (`.cer`, `.p12`, `.mobileprovisioning`, `.json`).
25
+ - Для всех прочих типов — базовая проверка (не пустой, читаемый), не блокирующая обработку.
26
+
27
+ 4) Тесты
28
+ - Расширить `spec/keysloth/file_manager_spec.rb`:
29
+ - кейс: собираются разные типы файлов (например, `.txt`, `.png`, `.bin`).
30
+ - кейс: игнорируются `.enc` и `.git` содержимое, а также `README.md`.
31
+ - кейс: корректный сбор в вложенных директориях.
32
+ - Адаптировать тесты, завязанные на whitelist.
33
+
34
+ 5) Обновить CLI help и генерацию README секретов
35
+ - Удалить жёсткий список «поддерживаемых типов» из long_desc в `lib/keysloth/cli.rb`.
36
+ - В шаблоне README для директории секретов указать: «поддерживаются любые типы файлов».
37
+ - Убедиться, что создаваемый локально `README.md` НЕ попадает под шифрование/отправку.
38
+
39
+ 6) Обновить основную документацию
40
+ - В `README.md` заменить раздел «Поддерживаемые типы файлов» на «любой тип файла (примеры: .cer, .p12, .json, изображения, бинарные и т.д.)».
41
+ - Добавить примечание про исключения (`*.enc`, `.git/**`, `.DS_Store`, `Thumbs.db`, `README.md`).
42
+
43
+ 7) Тест-план
44
+ - Обновить `task/test_plan.md`: добавить сценарии для произвольных расширений и бинарных файлов, проверку побайтной идентичности после `push`→`pull`.
45
+
46
+ 8) Релизная подготовка
47
+ - Обновить `CHANGELOG.md`.
48
+ - Поднять версию gem (patch/minor).
49
+
50
+ ## Критерии приёмки
51
+ - `push` шифрует все файлы из указанной директории (и подпапок), кроме исключений по умолчанию, и публикует их как `<relative_path>.enc`.
52
+ - `pull` корректно расшифровывает любые `.enc` и восстанавливает исходные имена и содержимое.
53
+ - Бинарные файлы (например, `.png`, `.p12`) после цикла `push`→`pull` побайтно идентичны исходникам (проверка через хэш).
54
+ - Тесты проходят локально и в CI.
55
+
56
+ ## Риски и меры
57
+ - Большие файлы: текущая реализация читает файлы целиком в память - так и оставляем;
58
+ - Случайные/мусорные файлы: добавлены исключения; при необходимости сделать настраиваемыми через конфиг - но это уже потом.
59
+
60
+ ## Замечания по обратной совместимости
61
+ - Прежние проекты продолжат работать; расширение функциональности не требует миграций.
62
+ - Пользователи могут удалить упоминания конкретных расширений из своих внутренних инструкций.
@@ -0,0 +1,49 @@
1
+ ## Исправление использования backup_count
2
+
3
+ Контекст:
4
+ - Значение `backup_count` из `.keyslothrc` не используется при создании бэкапов: `FileManager` всегда создаётся без параметра, из‑за чего применяется дефолт `3`.
5
+ - Требование: `backup_count == 0` — валидное значение, которое должно полностью отключать создание и ротацию бэкапов (без удаления уже существующих).
6
+
7
+ Цели:
8
+ - Прокидывать `backup_count` из конфигурации в `FileManager`.
9
+ - При `backup_count == 0` не создавать новые бэкапы и не выполнять очистку (не удалять существующие).
10
+
11
+ План работ
12
+
13
+ 1) Тесты (сначала красные)
14
+ - KeySloth.pull/KeySloth.push используют `backup_count` из конфигурации:
15
+ - В `spec/keysloth_spec.rb` добавить тест для `.pull` и `.push`, где `Config.load` возвращает конфиг с `backup_count: N` (произвольное число, например `7`).
16
+ - Ожидание: `KeySloth::FileManager.new` вызывается как `FileManager.new(logger, 7)`.
17
+
18
+ - Ротация с кастомным лимитом:
19
+ - В `spec/keysloth/file_manager_spec.rb` добавить тест: `file_manager = FileManager.new(logger, 2)`; создать директорию с секретами и трижды вызвать `create_backup` (небольшая задержка для уникальных timestamp'ов). Ожидание: `list_backups` вернёт 2 каталога.
20
+
21
+ - Отключение бэкапов (`backup_count == 0`):
22
+ - Тест: `file_manager = FileManager.new(logger, 0)`; вызвать `create_backup` для существующей директории. Ожидания: метод вернёт `nil`, новые каталоги бэкапов не появятся, `list_backups` пуст.
23
+
24
+ 2) Прогон тестов (ожидаемо красные)
25
+ ```bash
26
+ bundle exec rake spec
27
+ ```
28
+
29
+ 3) Исправления в коде
30
+ - `lib/keysloth.rb`:
31
+ - В `.pull` и `.push` заменить инициализацию `file_manager = FileManager.new(logger)` на `FileManager.new(logger, merged_config[:backup_count])`.
32
+ - Защититься от невалидных значений: если `backup_count` отсутствует/не Integer/отрицателен — использовать дефолт.
33
+
34
+ - `lib/keysloth/file_manager.rb`:
35
+ - В `create_backup`: если `@backup_count <= 0`, сразу возвращать `nil` (ничего не создавать и не чистить).
36
+ - В `cleanup_old_backups`: если `@backup_count <= 0`, сразу выходить (не удалять существующие бэкапы).
37
+
38
+ 4) Прогон тестов (ожидаемо зелёные)
39
+ ```bash
40
+ bundle exec rake spec
41
+ ```
42
+
43
+ Замечания и критерии приёмки
44
+ - Без `.keyslothrc` или без поля `backup_count` поведение не меняется: хранится 3 бэкапа.
45
+ - При `backup_count: 7` хранится 7 бэкапов (покрыто тестом на вызов конструктора и ротацию в `FileManager`).
46
+ - При `backup_count: 0` бэкапы не создаются и ротация не выполняется (существующие каталоги не удаляются).
47
+ - Никакие другие сценарии не ломаются, все существующие тесты остаются зелёными.
48
+
49
+
@@ -0,0 +1,47 @@
1
+ # Исправление: игнорируется branch/local_path из .keyslothrc при вызове CLI
2
+
3
+ ## Признаки
4
+ - При наличии `.keyslothrc` значения `branch` и `local_path` из файла конфигурации не применяются при `pull`/`push`.
5
+ - CLI прокидывает дефолты (`branch: 'main'`, `path: './secrets'`) в корневые методы, тем самым перекрывая конфиг.
6
+ - Автопоиск конфига есть (текущий каталог и `~/.keyslothrc`).
7
+
8
+ ## Требования (см. promts/main/rules.md)
9
+ - Добавить тест(ы), воспроизводящие баг (красные до фикса).
10
+ - Исправить код так, чтобы параметры из `.keyslothrc` учитывались, а CLI‑дефолты не перекрывали их.
11
+ - Убедиться, что все тесты проходят.
12
+ - Обновить `CHANGELOG.md` под текущую версию из `lib/keysloth/version.rb`.
13
+ - Обновить документацию: README — пояснить автопоиск `.keyslothrc`.
14
+
15
+ ## План работ
16
+ 1) Тесты (красные):
17
+ - В `spec/keysloth_spec.rb` добавить кейсы для `.pull` и `.push`:
18
+ - Мок `KeySloth::Config.load` возвращает конфиг с `branch: 'develop'`, `local_path: './conf_secrets'`.
19
+ - Вызов `.pull(repo_url: 'x', password: 'y', branch: nil, local_path: nil, config_file: '.keyslothrc')` должен после мерджа отдать `branch='develop'`, `local_path='./conf_secrets'` в `GitManager/FileManager`.
20
+ - Аналогично для `.push`.
21
+ - Альтернативно (на уровне CLI): тесты в `spec/keysloth/cli_spec.rb`, что при отсутствии указанных пользователем `--branch/--path` CLI не прокидывает дефолты, позволяя конфигу примениться.
22
+
23
+ 2) Исправление мерджа параметров:
24
+ - В `lib/keysloth/cli.rb`:
25
+ - Убрать дефолты у опций `:branch` и `:path` для команд `pull`/`push`/других, где нужно применять конфиг. Оставить только `desc` и `type`.
26
+ - В вызове `KeySloth.pull/push` передавать `branch: options[:branch], local_path: options[:path]` как есть (могут быть `nil`).
27
+ - В `lib/keysloth.rb` (методы `.pull` и `.push`):
28
+ - При формировании `merged_config = config.merge({...}.compact)` — НЕ задавать свои дефолты в сигнатуре метода (оставить `branch: nil` и `local_path: nil`), чтобы при `nil` бралось из `config`.
29
+ - Сохранять текущую логику приоритета: явные аргументы пользователя перекрывают конфиг, а `nil` не перетирает.
30
+
31
+ 3) Прогон тестов:
32
+ - `bundle exec rake spec` — все тесты должны стать зелёными.
33
+ - При необходимости — исправить тесты/код, не нарушая приоритетов параметров.
34
+
35
+ 4) Документация и ченджлог:
36
+ - В `README.md` дополнить раздел «Конфигурационный файл»:
37
+ - Явно указать автопоиск: текущая директория и `~/.keyslothrc`.
38
+ - Уточнить приоритеты: CLI > конфиг > значения по умолчанию.
39
+ - Явно указать дефолты при отсутствии флагов и полей в конфиге: `branch: 'main'`, `local_path: './secrets'`, `backup_count: 3` (у `repo_url` дефолта нет).
40
+ - В `CHANGELOG.md` для версии `0.2.0` (или актуальной) добавить запись:
41
+ - Исправлено: игнорирование `branch` и `local_path` из `.keyslothrc` при использовании CLI.
42
+
43
+ ## Критерии приёмки
44
+ - Без явных `--branch/--path` и с `.keyslothrc` значения из конфига применяются в `pull`/`push`.
45
+ - С явными флагами CLI значения из конфига перекрываются флагами.
46
+ - При отсутствии файла конфига — используются дефолты (из `Config::DEFAULT_CONFIG`).
47
+ - Тесты и линт проходят.
@@ -0,0 +1,77 @@
1
+ ## SSH fixes: улучшение UX и сообщений об ошибках
2
+
3
+ ### Цели
4
+ - Улучшить сообщения об ошибках SSH/аутентификации с практичными подсказками.
5
+ - Обновить README: явно указать поддержку `id_ed25519`, примеры использования ENV и `KEYSLOTH_SSH_KEY_PATH`.
6
+ - По возможности покрыть тестами типовые SSH‑ошибки.
7
+
8
+ ### Вне scope
9
+ - Не добавляем CLI‑флаг `--ssh-key`.
10
+ - Не обновляем test plan.
11
+
12
+ ---
13
+
14
+ ### План работ
15
+
16
+ #### 1) Документация (`README.md`)
17
+ - Обновить раздел «Настройка SSH ключей»:
18
+ - Ясно указать, что системный SSH и ssh-agent автоматически поддерживают как `id_rsa`, так и `id_ed25519`.
19
+ - Обновить раздел «CI/CD настройка»:
20
+ - Уточнить, что `SSH_PRIVATE_KEY` может содержать любой тип ключа (ed25519/rsa).
21
+ - Добавить упоминание `KEYSLOTH_SSH_KEY_PATH` для явного указания пути к ключу.
22
+ - Раздел «Использование системного SSH (GIT_SSH_COMMAND)»:
23
+ - Описать авто‑формирование `GIT_SSH_COMMAND` при наличии ENV (`SSH_PRIVATE_KEY`/`SSH_PUBLIC_KEY` или `KEYSLOTH_SSH_KEY_PATH`).
24
+ - Отдельно отметить рекомендации про StrictHostKeyChecking только для CI.
25
+ - Раздел «Troubleshooting → Ошибки аутентификации Git»:
26
+ - Добавить подсказки для Ed25519: проверка агента `ssh-add -l`, загрузка ключа `ssh-add ~/.ssh/id_ed25519`.
27
+ - Добавить подсказку про `KEYSLOTH_SSH_KEY_PATH` и ENV `SSH_PRIVATE_KEY` как быстрые способы указать ключ.
28
+
29
+ Готово, когда: README явно содержит `ed25519`, `KEYSLOTH_SSH_KEY_PATH` и универсальность `SSH_PRIVATE_KEY`.
30
+
31
+ #### 2) Улучшение ошибок SSH (код: `lib/keysloth/git_manager.rb`)
32
+ - В `run_git` добавить распознавание типовых SSH‑ошибок и расширенные советы:
33
+ - При `Permission denied (publickey)`:
34
+ - Советы: проверить ключи в агенте `ssh-add -l` (или загрузить: `ssh-add ~/.ssh/id_ed25519`),
35
+ указать ключ явно `KEYSLOTH_SSH_KEY_PATH=~/.ssh/id_ed25519` или использовать `SSH_PRIVATE_KEY` в CI,
36
+ проверить права на ключ `chmod 600 ~/.ssh/<key>`.
37
+ - При `Host key verification failed`:
38
+ - Советы: добавить хост в known_hosts `ssh-keyscan <host> >> ~/.ssh/known_hosts`,
39
+ избегать отключения проверки хостов вне CI.
40
+ - При `Repository not found`/`Could not read from remote repository`:
41
+ - Советы: проверить URL репозитория и доступ (приглашение/права).
42
+ - Сохранить текущее общее сообщение‑совет как fallback, если паттерн не распознан.
43
+ - Убедиться, что в сообщениях не выводятся секреты/ключи.
44
+
45
+ Готово, когда: `RepositoryError` содержит релевантные советы для распознанных паттернов.
46
+
47
+ #### 3) Тесты (по возможности) — `spec/keysloth/git_manager_spec.rb`
48
+ - Добавить группу: «SSH ошибки и подсказки».
49
+ - Кейс: stderr содержит `Permission denied (publickey)` → ошибка содержит подсказки про `ssh-add -l`, `KEYSLOTH_SSH_KEY_PATH`, `SSH_PRIVATE_KEY`.
50
+ - Кейс: stderr содержит `Host key verification failed` → ошибка содержит подсказку про `ssh-keyscan … >> ~/.ssh/known_hosts`.
51
+ - Кейс: stderr содержит `Repository not found` → ошибка содержит подсказку про проверку URL/прав доступа.
52
+ - Кейс: произвольный stderr → остаётся общий совет.
53
+ - Техподход:
54
+ - Мокнуть `Open3.capture3` на неуспех с требуемым stderr.
55
+ - Вызвать любой публичный путь, который триггерит `run_git` (например, `clone_repository`/`ensure_branch_up_to_date`) или обратиться через `send` к приватному методу, не ломая интерфейс.
56
+
57
+ Готово, когда: новые спеки зелёные и не ломают существующие.
58
+
59
+ #### 4) CHANGELOG
60
+ - Добавить строку: «Улучшены сообщения об ошибках SSH (подсказки для publickey/host key/доступа), README обновлён под Ed25519 и KEYSLOTH_SSH_KEY_PATH».
61
+
62
+ #### 5) Быстрая ручная проверка
63
+ - `KEYSLOTH_SSH_KEY_PATH` с реальным ed25519 ключом → `pull`/`push` проходят.
64
+ - Без ENV, c системным ssh-agent и `id_ed25519` → работает «из коробки».
65
+ - Смоделировать ошибку `Permission denied (publickey)` и убедиться, что подсказки видны и полезны.
66
+
67
+ ---
68
+
69
+ ### Критерии приёмки
70
+ - README обновлён (ed25519, KEYSLOTH_SSH_KEY_PATH, ENV‑примеры).
71
+ - Сообщения об ошибках SSH расширены и сохраняют безопасность.
72
+ - Добавлены/прогнаны спеки на типовые сценарии (если возможно).
73
+ - CHANGELOG обновлён.
74
+
75
+ ### Риски и откат
76
+ - Риск «шумных» сообщений для редких ошибок — ограничиваемся известными паттернами.
77
+ - Откат: вернуть прежнюю версию `run_git`, удалить новые спеки и строку в CHANGELOG; документацию откатить соответствующим PR.
@@ -0,0 +1,38 @@
1
+ # Обновление диапазона версии thor
2
+
3
+ Цель: обеспечить совместимость с thor в диапазоне '>= 1.0.1', '< 1.4.0' (консервативно), сохранив работоспособность и покрытие тестами.
4
+
5
+ ## Таск-лист (выполнить по порядку)
6
+
7
+ 1) Подготовка и анализ
8
+ - Проверить текущие точки интеграции с Thor в `lib/keysloth/cli.rb` (описания опций, help, ошибки)
9
+ - Сверить breaking changes Thor 1.0.x → 1.3.x (release notes)
10
+
11
+ 2) Обновить зависимость
12
+ - В `keysloth.gemspec` заменить зависимость на: `spec.add_dependency 'thor', '>= 1.0.1', '< 1.4.0'`
13
+ - Обновить локальные зависимости: `bundle update thor` (для проверки на последнем доступном < 1.4.0)
14
+
15
+ 3) Матрица совместимости (локально и/или CI)
16
+ - Добавить Appraisal (dev): `spec.add_development_dependency 'appraisal', '~> 2.5'`
17
+ - Создать `Appraisals` с наборами:
18
+ - thor-1.0: `gem 'thor', '1.0.1'`
19
+ - thor-1.3: `gem 'thor', '~> 1.3.2'`
20
+ - Установить и прогнать: `bundle exec appraisal install`
21
+ - Запустить тесты: `bundle exec appraisal thor-1.0 rspec` и `bundle exec appraisal thor-1.3 rspec`
22
+
23
+ 4) Тесты (согласно правилам выполнения фичи)
24
+ - Убедиться, что весь набор тестов проходит на обоих наборах (1.0.1 и ~>1.3.2)
25
+ - При падениях — адаптировать код CLI или тесты под совместимый API Thor, не меняя публичное поведение
26
+
27
+ 5) Качество кода
28
+ - Прогнать линтер: `bundle exec rake rubocop`
29
+ - Прогнать полную проверку: `bundle exec rake check`
30
+
31
+ 6) Документация
32
+ - Обновить `CHANGELOG.md` для текущей версии из `lib/keysloth/version.rb` (раздел Changed):
33
+ - «Диапазон совместимости Thor обновлён до '>= 1.0.1', '< 1.4.0'»
34
+
35
+ ## Критерии приёмки
36
+ - Тесты проходят на `thor 1.0.1` и на `thor ~> 1.3.2`
37
+ - Никаких регрессий CLI (опции, help, коды завершения)
38
+ - Обновлены: `gemspec` (диапазон), `CHANGELOG.md`
@@ -0,0 +1,60 @@
1
+ ### Чек‑лист релиза
2
+
3
+ Краткий, практичный список шагов для публикации и релиза `KeySloth`.
4
+
5
+ #### 1) Подготовка версии
6
+ - [ ] Обновить версию в `lib/keysloth/version.rb` (SemVer)
7
+ - [ ] Обновить `CHANGELOG.md` (секция для новой версии)
8
+ - [ ] Проверить `keysloth.gemspec`: `summary`, `description`, `authors`, `email`, `license`, `required_ruby_version`, `homepage`
9
+ - [ ] Добавить/актуализировать `metadata`
10
+
11
+ #### 2) Публикация на RubyGems.org
12
+ - [ ] Создать аккаунт/включить 2FA (MFA) на RubyGems
13
+ - [ ] Сгенерировать API‑ключ и экспортировать его в окружение
14
+ ```bash
15
+ export GEM_HOST_API_KEY=rg_********************************
16
+ ```
17
+ - [ ] Собрать и проверить пакет
18
+ ```bash
19
+ gem build keysloth.gemspec
20
+ gem check keysloth-<VERSION>.gem
21
+ ```
22
+ - [ ] Отправить пакет
23
+ ```bash
24
+ gem push keysloth-<VERSION>.gem
25
+ ```
26
+ - [ ] Проверить страницу gem’a: `https://rubygems.org/gems/keysloth`
27
+ - [ ] (Опционально) Отозвать версию (rollback)
28
+ ```bash
29
+ gem yank keysloth -v <VERSION>
30
+ ```
31
+
32
+ #### 3) Git‑тег и GitHub Release
33
+ - [ ] Создать и запушить тег
34
+ - [ ] Создать релиз (через GitHub UI)
35
+
36
+ #### 4) Валидация после релиза
37
+ - [ ] Проверить, что страница на RubyGems отображает новую версию
38
+ - [ ] Проверить установку:
39
+ ```bash
40
+ gem install keysloth -v <VERSION>
41
+ keysloth version
42
+ keysloth help
43
+ ```
44
+ - [ ] Прогнать быстрый сценарий использования (минимальный pull в тестовый каталог)
45
+
46
+ #### 5) Rollback (при необходимости)
47
+ - [ ] Отозвать версию gem’a:
48
+ ```bash
49
+ gem yank keysloth -v <VERSION>
50
+ ```
51
+ - [ ] Удалить GitHub Release и тег (или выпустить hotfix)
52
+
53
+ #### 6) Мини‑чек‑лист
54
+ - [ ] Обновить версию и changelog
55
+ - [ ] Собрать и запушить gem в RubyGems
56
+ - [ ] Создать git‑тег и GitHub Release
57
+ - [ ] Обновить/опубликовать документацию
58
+ - [ ] Обновить/добавить примеры CI/CD
59
+
60
+