feat(ops): проверка бэкапа и уборка файлов без записи

- glchat verify-backup: расшифровка age, integrity_check, foreign_key_check,
  схема и состав данных, сверка с манифестом; живые данные не трогает
- чистка удаляет файлы на диске, у которых не осталось записи в базе (так
  оставались вложения удалённых серверов), и пустые подкаталоги; работает
  через os.Root, чтобы символическая ссылка не вывела за каталог файлов
- удаление сервера теперь чистит и содержимое файлов, а не только записи
- install.sh пишет значения .env с пробелами в кавычках: файл подключается
  через `set -a && . .env`, иначе имя главного сервера выполнялось как команда
- тесты: Go на уборку файлов без записи (свежие не трогаются, пустые
  каталоги удаляются)
This commit is contained in:
2026-09-21 23:33:04 +03:00
parent ef871bcd96
commit 1cb949a7a4
8 changed files with 329 additions and 4 deletions
+41
View File
@@ -164,3 +164,44 @@ func scanFile(scanner interface{ Scan(...any) error }) (*File, error) {
file.CreatedAt = parseTimestamp(createdAt)
return &file, nil
}
// FileStoragePaths возвращает пути всех файлов, известных базе: по ним
// обслуживание понимает, какие файлы на диске остались без записи.
func (s *Store) FileStoragePaths(ctx context.Context) (map[string]bool, error) {
rows, err := s.reader.QueryContext(ctx, `SELECT storage_path FROM files WHERE storage_path <> ''`)
if err != nil {
return nil, mapError(err)
}
defer rows.Close()
paths := map[string]bool{}
for rows.Next() {
var path string
if err := rows.Scan(&path); err != nil {
return nil, mapError(err)
}
paths[path] = true
}
return paths, rows.Err()
}
// GuildStoragePaths возвращает пути файлов сервера: их нужно удалить с диска
// перед удалением сервера, иначе содержимое останется мусором (AGENT.md 6.4).
func (s *Store) GuildStoragePaths(ctx context.Context, guildID uint64) ([]string, error) {
rows, err := s.reader.QueryContext(ctx,
`SELECT storage_path FROM files WHERE guild_id = ? AND storage_path <> ''`, int64(guildID))
if err != nil {
return nil, mapError(err)
}
defer rows.Close()
paths := make([]string, 0, 8)
for rows.Next() {
var path string
if err := rows.Scan(&path); err != nil {
return nil, err
}
paths = append(paths, path)
}
return paths, rows.Err()
}