Class: KeySloth::FileManager

Inherits:
Object
  • Object
show all
Defined in:
lib/keysloth/file_manager.rb

Overview

Класс для управления файловыми операциями KeySloth

Обеспечивает создание директорий, чтение и запись файлов, создание резервных копий и валидацию путей. Поддерживает работу с различными типами секретных файлов.

Examples:

Использование

file_manager = KeySloth::FileManager.new

# Создание директории
file_manager.ensure_directory('./secrets')

# Сбор файлов секретов
files = file_manager.collect_secret_files('./secrets')

# Создание backup'а
file_manager.create_backup('./secrets')

Author:

  • KeySloth Team

Since:

  • 0.1.0

Constant Summary collapse

DEFAULT_BACKUP_COUNT =

Максимальное количество backup'ов

Since:

  • 0.1.0

3

Instance Method Summary collapse

Constructor Details

#initialize(logger = nil, backup_count = DEFAULT_BACKUP_COUNT) ⇒ FileManager

Инициализация файлового менеджера

Parameters:

  • logger (KeySloth::Logger) (defaults to: nil) —

    Логгер для вывода сообщений

  • backup_count (Integer) (defaults to: DEFAULT_BACKUP_COUNT) —

    Количество backup'ов для хранения

Since:

  • 0.1.0



36
37
38
39
# File 'lib/keysloth/file_manager.rb', line 36

def initialize(logger = nil, backup_count = DEFAULT_BACKUP_COUNT)
  @logger = logger || Logger.new(level: :error)
  @backup_count = backup_count
end

Instance Method Details

#collect_secret_files(directory_path) ⇒ Array<String>

Собирает все файлы секретов из директории (wildcard)

Рекурсивно собирает любые обычные файлы, исключая:

  • .enc файлы (они являются артефактами репозитория)
  • содержимое .git директорий
  • общеизвестные мусорные файлы (.DS_Store, Thumbs.db)
  • локальный README.md внутри каталога секретов

Parameters:

  • directory_path (String) —

    Путь к директории с секретами

Returns:

  • (Array<String>) —

    Массив путей к файлам секретов

Raises:

  • (FileSystemError) —

    при ошибках доступа к директории

Since:

  • 0.1.0



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/keysloth/file_manager.rb', line 117

def collect_secret_files(directory_path)
  @logger.debug("Собираем файлы секретов из: #{directory_path}")

  begin
    validate_directory_access!(directory_path)

    all_candidates = Dir.glob(File.join(directory_path, '**', '*'), File::FNM_DOTMATCH)

    files = all_candidates.select do |path|
      next false unless File.file?(path)

      relative = get_relative_path(path, directory_path)

      # Исключаем .git содержимое
      next false if relative.split(File::SEPARATOR).include?('.git')

      # Исключаем артефакты шифрования
      next false if File.extname(path).downcase == '.enc'

      # Исключаем общеизвестный мусор и локальный README.md
      base = File.basename(path)
      next false if base == '.DS_Store' || base == 'Thumbs.db' || base == 'README.md'

      true
    end

    @logger.info("Найдено #{files.size} файлов секретов")
    files.sort
  rescue StandardError => e
    @logger.error('Ошибка сбора файлов секретов', e)
    raise FileSystemError, "Не удалось собрать файлы секретов: #{e.message}"
  end
end

#create_backup(directory_path) ⇒ String?

Создает резервную копию директории

Parameters:

  • directory_path (String) —

    Путь к директории для backup'а

Returns:

  • (String, nil) —

    Путь к созданному backup'у или nil если директория не существует

Raises:

Since:

  • 0.1.0



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/keysloth/file_manager.rb', line 165

def create_backup(directory_path)
  return nil unless directory_exists?(directory_path)
  # Отключение бэкапов: при нулевом или отрицательном лимите
  return nil if @backup_count.to_i <= 0

  timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
  backup_name = "#{File.basename(directory_path)}_backup_#{timestamp}"
  backup_path = File.join(File.dirname(directory_path), backup_name)

  @logger.info("Создаем backup: #{backup_path}")

  begin
    FileUtils.cp_r(directory_path, backup_path)

    # Очищаем старые backup'ы
    cleanup_old_backups(File.dirname(directory_path), File.basename(directory_path))

    @logger.debug("Backup успешно создан: #{backup_path}")
    backup_path
  rescue StandardError => e
    @logger.error('Ошибка создания backup', e)
    raise FileSystemError, "Не удалось создать backup #{directory_path}: #{e.message}"
  end
end

#directory_exists?(path) ⇒ Boolean

Проверяет существование директории

Parameters:

  • path (String) —

    Путь к директории

Returns:

  • (Boolean) —

    true если директория существует

Since:

  • 0.1.0



63
64
65
# File 'lib/keysloth/file_manager.rb', line 63

def directory_exists?(path)
  File.directory?(path)
end

#ensure_directory(path) ⇒ Object

Обеспечивает существование директории

Parameters:

  • path (String) —

    Путь к директории

Raises:

  • (FileSystemError) —

    при ошибках создания директории

Since:

  • 0.1.0



45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/keysloth/file_manager.rb', line 45

def ensure_directory(path)
  return if directory_exists?(path)

  @logger.info("Создаем директорию: #{path}")

  begin
    FileUtils.mkdir_p(path)
    @logger.debug("Директория успешно создана: #{path}")
  rescue StandardError => e
    @logger.error('Ошибка создания директории', e)
    raise FileSystemError, "Не удалось создать директорию #{path}: #{e.message}"
  end
end

#get_relative_path(file_path, base_path) ⇒ String

Возвращает относительный путь файла

Parameters:

  • file_path (String) —

    Полный путь к файлу

  • base_path (String) —

    Базовый путь

Returns:

  • (String) —

    Относительный путь

Since:

  • 0.1.0



156
157
158
# File 'lib/keysloth/file_manager.rb', line 156

def get_relative_path(file_path, base_path)
  Pathname.new(file_path).relative_path_from(Pathname.new(base_path)).to_s
end

#list_backups(directory_path) ⇒ Array<String>

Возвращает список доступных backup'ов

Parameters:

  • directory_path (String) —

    Путь к директории

Returns:

  • (Array<String>) —

    Массив путей к backup'ам (отсортированный по времени)

Since:

  • 0.1.0



218
219
220
221
222
223
224
225
226
227
# File 'lib/keysloth/file_manager.rb', line 218

def list_backups(directory_path)
  base_name = File.basename(directory_path)
  parent_dir = File.dirname(directory_path)
  backup_pattern = File.join(parent_dir, "#{base_name}_backup_*")

  Dir.glob(backup_pattern)
    .select { |path| File.directory?(path) }
    .sort
    .reverse # Новые backup'ы первыми
end

#read_file(file_path) ⇒ String

Читает содержимое файла

Parameters:

  • file_path (String) —

    Путь к файлу

Returns:

  • (String) —

    Содержимое файла

Raises:

Since:

  • 0.1.0



72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/keysloth/file_manager.rb', line 72

def read_file(file_path)
  @logger.debug("Читаем файл: #{file_path}")

  begin
    content = File.binread(file_path)
    @logger.debug("Файл прочитан успешно (размер: #{content.length} байт)")
    content
  rescue StandardError => e
    @logger.error('Ошибка чтения файла', e)
    raise FileSystemError, "Не удалось прочитать файл #{file_path}: #{e.message}"
  end
end

#restore_from_backup(backup_path, target_path) ⇒ Object

Восстанавливает из backup'а

Parameters:

  • backup_path (String) —

    Путь к backup'у

  • target_path (String) —

    Путь для восстановления

Raises:

Since:

  • 0.1.0



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/keysloth/file_manager.rb', line 195

def restore_from_backup(backup_path, target_path)
  @logger.info("Восстанавливаем из backup: #{backup_path}")

  begin
    validate_backup_path!(backup_path)

    # Удаляем существующую директорию
    FileUtils.rm_rf(target_path)

    # Копируем из backup'а
    FileUtils.cp_r(backup_path, target_path)

    @logger.info('Восстановление из backup завершено')
  rescue StandardError => e
    @logger.error('Ошибка восстановления из backup', e)
    raise FileSystemError, "Не удалось восстановить из backup: #{e.message}"
  end
end

#validate_file_path!(file_path) ⇒ Object

Валидирует путь файла

Parameters:

  • file_path (String) —

    Путь к файлу

Raises:

Since:

  • 0.1.0



299
300
301
302
303
304
305
306
307
308
309
# File 'lib/keysloth/file_manager.rb', line 299

def validate_file_path!(file_path)
  if file_path.nil? || file_path.empty?
    raise ValidationError, 'Путь к файлу не может быть пустым'
  end

  # Проверяем на потенциально опасные пути
  normalized_path = File.expand_path(file_path)
  if normalized_path.include?('..')
    raise ValidationError, 'Путь содержит потенциально опасные элементы'
  end
end

#verify_file_integrity(file_path) ⇒ Boolean

Проверяет целостность файла секрета

Parameters:

  • file_path (String) —

    Путь к файлу

Returns:

  • (Boolean) —

    true если файл прошел проверку целостности

Since:

  • 0.1.0



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/keysloth/file_manager.rb', line 233

def verify_file_integrity(file_path)
  return false unless File.exist?(file_path)
  return false if File.empty?(file_path)

  # Базовая проверка на читаемость
  begin
    content = File.binread(file_path, 100) # Читаем первые 100 байт для проверки

    # Дополнительная проверка по типу файла
    file_extension = File.extname(file_path).downcase
    verify_file_type_integrity(content, file_extension)
  rescue StandardError => e
    @logger.debug("Ошибка проверки целостности файла #{file_path}: #{e.message}")
    false
  end
end

#verify_file_integrity_detailed(file_path) ⇒ Hash

Выполняет детальную проверку целостности файла

Parameters:

  • file_path (String) —

    Путь к файлу

Returns:

  • (Hash) —

    Детальный результат проверки

Since:

  • 0.1.0



254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# File 'lib/keysloth/file_manager.rb', line 254

def verify_file_integrity_detailed(file_path)
  result = {
    valid: false,
    exists: false,
    readable: false,
    non_empty: false,
    type_valid: false,
    size: 0,
    error: nil
  }

  begin
    # Проверяем существование
    result[:exists] = File.exist?(file_path)
    return result unless result[:exists]

    # Проверяем размер
    result[:size] = File.size(file_path)
    result[:non_empty] = result[:size].positive?
    return result unless result[:non_empty]

    # Проверяем читаемость
    content = File.binread(file_path, 200) # Читаем больше для детальной проверки
    result[:readable] = true

    # Проверяем соответствие типу файла
    file_extension = File.extname(file_path).downcase
    result[:type_valid] = verify_file_type_integrity(content, file_extension)

    result[:valid] = result[:exists] && result[:readable] &&
                     result[:non_empty] && result[:type_valid]

    @logger.debug("Детальная проверка файла #{file_path}: #{result}")
  rescue StandardError => e
    result[:error] = e.message
    @logger.debug("Ошибка детальной проверки файла #{file_path}: #{e.message}")
  end

  result
end

#write_file(file_path, content) ⇒ Object

Записывает содержимое в файл

Parameters:

  • file_path (String) —

    Путь к файлу

  • content (String) —

    Содержимое для записи

Raises:

Since:

  • 0.1.0



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/keysloth/file_manager.rb', line 90

def write_file(file_path, content)
  @logger.debug("Записываем файл: #{file_path}")

  begin
    # Создаем директорию если не существует
    directory = File.dirname(file_path)
    ensure_directory(directory) unless directory_exists?(directory)

    File.binwrite(file_path, content)
    @logger.debug("Файл записан успешно (размер: #{content.length} байт)")
  rescue StandardError => e
    @logger.error('Ошибка записи файла', e)
    raise FileSystemError, "Не удалось записать файл #{file_path}: #{e.message}"
  end
end