From fda87c63fd71d69bb3243535287093716382ed15 Mon Sep 17 00:00:00 2001 From: RarDog Date: Sun, 13 Sep 2026 19:08:53 +0300 Subject: [PATCH] fix: Detect broken Paru linked against old libalpm and repair via official repo/makepkg --- lib/src/services/catalog_service.dart | 12 -- .../services/component_installer_service.dart | 186 ++++++++++-------- lib/src/ui/screens/settings_screen.dart | 29 ++- lib/src/ui/widgets/setup_wizard_dialog.dart | 14 +- 4 files changed, 140 insertions(+), 101 deletions(-) diff --git a/lib/src/services/catalog_service.dart b/lib/src/services/catalog_service.dart index 7763d6c..49b91bf 100644 --- a/lib/src/services/catalog_service.dart +++ b/lib/src/services/catalog_service.dart @@ -282,18 +282,6 @@ class CatalogService { rating: 4.95, votes: 1256, ), - CatalogApp( - id: 'paru-bin', - name: 'Paru (Binary)', - summary: 'Прекомпилированная бинарная версия AUR-хелпера Paru для быстрой установки без сборки.', - category: 'Утилиты', - backend: BackendType.aur, - iconUrl: _knownIconUrls['paru-bin'], - fallbackIcon: CupertinoIcons.flame_fill, - accentColor: const Color(0xFFFF9500), - rating: 4.9, - votes: 324, - ), CatalogApp( id: 'com.usebottles.bottles', name: 'Bottles', diff --git a/lib/src/services/component_installer_service.dart b/lib/src/services/component_installer_service.dart index 5cac73b..5e0e43b 100644 --- a/lib/src/services/component_installer_service.dart +++ b/lib/src/services/component_installer_service.dart @@ -9,12 +9,16 @@ class SystemComponentStatus { final bool hasFlathub; final bool hasParu; final bool hasYay; + final bool isParuBroken; + final String? paruBrokenReason; const SystemComponentStatus({ required this.hasFlatpak, required this.hasFlathub, required this.hasParu, required this.hasYay, + this.isParuBroken = false, + this.paruBrokenReason, }); bool get hasAurHelper => hasParu || hasYay; @@ -27,6 +31,8 @@ class ComponentInstallerService { bool hasFlathub = false; bool hasParu = false; bool hasYay = false; + bool isParuBroken = false; + String? paruBrokenReason; try { final res = await Process.run('which', ['flatpak']); @@ -41,8 +47,17 @@ class ComponentInstallerService { } try { + final whichRes = await Process.run('which', ['paru']); + final hasParuBinary = whichRes.exitCode == 0 && whichRes.stdout.toString().trim().isNotEmpty; + final res = await Process.run('paru', ['--version']); - hasParu = res.exitCode == 0 && res.stdout.toString().toLowerCase().contains('paru'); + if (res.exitCode == 0 && res.stdout.toString().toLowerCase().contains('paru')) { + hasParu = true; + } else if (hasParuBinary) { + isParuBroken = true; + final err = res.stderr.toString().trim(); + paruBrokenReason = err.isNotEmpty ? err : 'Несовместимость библиотек libalpm'; + } } catch (_) {} try { @@ -55,72 +70,53 @@ class ComponentInstallerService { hasFlathub: hasFlathub, hasParu: hasParu, hasYay: hasYay, + isParuBroken: isParuBroken, + paruBrokenReason: paruBrokenReason, ); } /// Установка Flatpak и подключение репозитория Flathub static Stream installFlatpak(String? password) async* { - yield '🚀 [Flatpak] Проверка и установка пакета flatpak через pacman...'; + yield '🚀 [Flatpak] Начинаем установку Flatpak...'; - // 1. Установка flatpak - final proc = await AdminService.runPrivileged( + // 1. Установка пакета flatpak через pacman + yield '📦 [Flatpak] Установка системного пакета flatpak...'; + final pacmanProc = await AdminService.runPrivileged( password, 'pacman', ['-S', '--needed', '--noconfirm', 'flatpak'], ); final controller = StreamController(); - proc.stdout.transform(utf8.decoder).transform(const LineSplitter()).listen( - (line) => controller.add(line), - onError: (e) => controller.add('ERR: $e'), - ); - proc.stderr.transform(utf8.decoder).transform(const LineSplitter()).listen( - (line) => controller.add('STDERR: $line'), - onError: (e) => controller.add('ERR: $e'), - ); - - proc.exitCode.then((_) { - Future.delayed(const Duration(milliseconds: 60), () { - if (!controller.isClosed) controller.close(); - }); - }); + pacmanProc.stdout.transform(utf8.decoder).transform(const LineSplitter()).listen((l) => controller.add(l)); + pacmanProc.stderr.transform(utf8.decoder).transform(const LineSplitter()).listen((l) => controller.add(l)); + pacmanProc.exitCode.then((_) => Future.delayed(const Duration(milliseconds: 50), () => controller.close())); await for (final line in controller.stream) { yield line; } - final code = await proc.exitCode; - if (code != 0) { - yield '❌ [Flatpak] Ошибка установки пакета flatpak (код $code).'; + final pacmanExit = await pacmanProc.exitCode; + if (pacmanExit != 0) { + yield '❌ [Flatpak] Ошибка установки pacman (код $pacmanExit)'; return; } - yield '✅ [Flatpak] Пакет flatpak успешно установлен в систему.'; - yield '🌐 [Flathub] Подключение официального репозитория Flathub...'; + yield '✅ [Flatpak] Пакет flatpak установлен!'; - // 2. Добавление репозитория Flathub - try { - final remoteProc = await Process.run('flatpak', [ - 'remote-add', - '--if-not-exists', - 'flathub', - 'https://dl.flathub.org/repo/flathub.flatpakrepo', - ]); + // 2. Подключение Flathub + yield '🌐 [Flathub] Подключение репозитория Flathub...'; + final flathubRes = await Process.run('flatpak', [ + 'remote-add', + '--if-not-exists', + 'flathub', + 'https://dl.flathub.org/repo/flathub.flatpakrepo', + ]); - if (remoteProc.exitCode == 0) { - yield '✅ [Flathub] Репозиторий Flathub успешно подключен!'; - } else { - // Попытка с правами суперпользователя - final rootRemote = await AdminService.runPrivileged( - password, - 'flatpak', - ['remote-add', '--if-not-exists', 'flathub', 'https://dl.flathub.org/repo/flathub.flatpakrepo'], - ); - await rootRemote.exitCode; - yield '✅ [Flathub] Репозиторий Flathub подключен системно!'; - } - } catch (e) { - yield '⚠️ [Flathub] Предупреждение при подключении репозитория: $e'; + if (flathubRes.exitCode == 0) { + yield '✅ [Flathub] Репозиторий Flathub успешно добавлен!'; + } else { + yield '⚠️ [Flathub] Сообщение: ${flathubRes.stderr.toString().trim()}'; } yield '🎉 [Успех] Интеграция Flatpak & Flathub полностью готова!'; @@ -128,15 +124,59 @@ class ComponentInstallerService { /// Установка AUR-хелпера Paru (Rust) static Stream installParu(String? password) async* { - yield '🚀 [Paru] Начинаем установку Paru (AUR Helper на Rust)...'; + yield '🚀 [Paru] Начинаем установку / восстановление Paru (AUR Helper на Rust)...'; - // Если уже есть yay, ставим paru-bin через него + // 1. Проверяем наличие paru в официальных/дистрибутивных репозиториях pacman (EndeavourOS, Chaotic-AUR и т.д.) + bool inPacman = false; + try { + final chk = await Process.run('pacman', ['-Si', 'paru']); + inPacman = chk.exitCode == 0; + } catch (_) {} + + if (inPacman) { + yield '📦 [Paru] Обнаружен нативный пакет paru в репозитории системы! Устанавливаем через pacman...'; + try { + final pacmanProc = await AdminService.runPrivileged( + password, + 'pacman', + ['-S', '--noconfirm', 'paru'], + ); + + final controller = StreamController(); + pacmanProc.stdout.transform(utf8.decoder).transform(const LineSplitter()).listen((l) => controller.add(l)); + pacmanProc.stderr.transform(utf8.decoder).transform(const LineSplitter()).listen((l) => controller.add(l)); + pacmanProc.exitCode.then((_) => Future.delayed(const Duration(milliseconds: 50), () => controller.close())); + + await for (final line in controller.stream) { + yield line; + } + + final exitCode = await pacmanProc.exitCode; + if (exitCode == 0) { + final verify = await Process.run('paru', ['--version']); + if (verify.exitCode == 0) { + AurService.resetCache(); + yield '🎉 [Paru] Paru успешно установлен и проверен (${verify.stdout.toString().trim()})!'; + return; + } + } + yield '⚠️ [Paru] Установка через pacman не сработала (код $exitCode), переходим к сборке...'; + } catch (e) { + yield '⚠️ [Paru] Ошибка pacman: $e'; + } + } + + // 2. Если есть рабочий yay, собираем нативный paru через yay (НЕ paru-bin!) final status = await checkComponents(); if (status.hasYay) { - yield '📦 [Paru] Обнаружен yay! Устанавливаем paru-bin через yay...'; + yield '📦 [Paru] Обнаружен yay! Сборка и установка нативного paru через yay...'; + if (password != null && password.isNotEmpty) { + await AdminService.verifyPassword(password); + } + final proc = await Process.start( 'yay', - ['-S', '--noconfirm', 'paru-bin'], + ['-S', '--noconfirm', 'paru'], environment: {'LC_ALL': 'C'}, ); @@ -162,38 +202,22 @@ class ComponentInstallerService { final exitCode = await proc.exitCode; if (exitCode == 0) { - AurService.resetCache(); - yield '🎉 [Paru] Paru успешно установлен и готов к работе!'; - return; - } else { - yield '⚠️ [Paru] Не удалось установить через yay (код $exitCode), пробуем сборку из исходников paru...'; - final procSrc = await Process.start( - 'yay', - ['-S', '--noconfirm', 'paru'], - environment: {'LC_ALL': 'C'}, - ); - final srcCtrl = StreamController(); - procSrc.stdout.transform(utf8.decoder).transform(const LineSplitter()).listen((l) => srcCtrl.add(l)); - procSrc.stderr.transform(utf8.decoder).transform(const LineSplitter()).listen((l) => srcCtrl.add(l)); - procSrc.exitCode.then((_) => Future.delayed(const Duration(milliseconds: 50), () => srcCtrl.close())); - await for (final l in srcCtrl.stream) { - yield l; - } - final srcExit = await procSrc.exitCode; - if (srcExit == 0) { + final verify = await Process.run('paru', ['--version']); + if (verify.exitCode == 0) { AurService.resetCache(); - yield '🎉 [Paru] Paru успешно скомпилирован и установлен!'; + yield '🎉 [Paru] Paru успешно скомпилирован и готов к работе (${verify.stdout.toString().trim()})!'; return; } } + yield '⚠️ [Paru] Установка через yay завершилась с кодом $exitCode, пробуем makepkg...'; } - // Ручная установка paru-bin через makepkg - yield '🛠️ [Paru] Проверка базовых инструментов сборки (base-devel, git)...'; + // 3. Ручная сборка исходников paru через makepkg + yield '🛠️ [Paru] Проверка базовых инструментов сборки (base-devel, git, cargo)...'; final pacmanProc = await AdminService.runPrivileged( password, 'pacman', - ['-S', '--needed', '--noconfirm', 'base-devel', 'git'], + ['-S', '--needed', '--noconfirm', 'base-devel', 'git', 'cargo'], ); await pacmanProc.exitCode; @@ -205,12 +229,12 @@ class ComponentInstallerService { Directory? tempDir; try { tempDir = Directory.systemTemp.createTempSync('forge_paru_install_'); - yield '📥 [Paru] Клонирование paru-bin из AUR...'; + yield '📥 [Paru] Клонирование исходников paru из AUR...'; final cloneRes = await Process.run('git', [ 'clone', - 'https://aur.archlinux.org/paru-bin.git', - '${tempDir.path}/paru-bin', + 'https://aur.archlinux.org/paru.git', + '${tempDir.path}/paru', ]); if (cloneRes.exitCode != 0) { @@ -218,11 +242,11 @@ class ComponentInstallerService { return; } - yield '⚙️ [Paru] Сборка и установка пакета (makepkg -si)...'; + yield '⚙️ [Paru] Сборка и установка пакета через makepkg -si...'; final makepkgProc = await Process.start( 'makepkg', ['-si', '--noconfirm'], - workingDirectory: '${tempDir.path}/paru-bin', + workingDirectory: '${tempDir.path}/paru', environment: {'LC_ALL': 'C'}, ); @@ -248,7 +272,13 @@ class ComponentInstallerService { final exitCode = await makepkgProc.exitCode; if (exitCode == 0) { - yield '🎉 [Paru] Paru успешно собран и установлен в систему!'; + final verify = await Process.run('paru', ['--version']); + if (verify.exitCode == 0) { + AurService.resetCache(); + yield '🎉 [Paru] Paru успешно собран и установлен в систему!'; + } else { + yield '❌ [Paru] Пакет установлен, но не может запуститься: ${verify.stderr}'; + } } else { yield '❌ [Paru] makepkg завершился с кодом ошибки $exitCode.'; } diff --git a/lib/src/ui/screens/settings_screen.dart b/lib/src/ui/screens/settings_screen.dart index 102ba88..d27482b 100644 --- a/lib/src/ui/screens/settings_screen.dart +++ b/lib/src/ui/screens/settings_screen.dart @@ -438,28 +438,41 @@ class _SettingsScreenState extends State { decoration: BoxDecoration( color: (_componentStatus?.hasParu ?? false) ? CupertinoColors.systemGreen.withValues(alpha: 0.15) - : CupertinoColors.systemGrey.withValues(alpha: 0.15), + : ((_componentStatus?.isParuBroken ?? false) + ? CupertinoColors.systemRed.withValues(alpha: 0.15) + : CupertinoColors.systemGrey.withValues(alpha: 0.15)), borderRadius: BorderRadius.circular(4), ), child: Text( (_componentStatus?.hasParu ?? false) ? 'Paru активен' - : ((_componentStatus?.hasYay ?? false) ? 'Обнаружен yay' : 'Отсутствует'), + : ((_componentStatus?.isParuBroken ?? false) + ? 'Повреждён (libalpm)' + : ((_componentStatus?.hasYay ?? false) ? 'Обнаружен yay' : 'Отсутствует')), style: TextStyle( fontSize: 10.5, fontWeight: FontWeight.bold, color: (_componentStatus?.hasParu ?? false) ? CupertinoColors.systemGreen - : CupertinoColors.systemGrey, + : ((_componentStatus?.isParuBroken ?? false) + ? CupertinoColors.systemRed + : CupertinoColors.systemGrey), ), ), ), ], ), const SizedBox(height: 2), - const Text( - 'Сверхбыстрый сборщик пакетов из Arch User Repository', - style: TextStyle(fontSize: 12, color: CupertinoColors.systemGrey), + Text( + (_componentStatus?.isParuBroken ?? false) + ? '⚠️ Paru поврежден (несовместимость libalpm в Arch). Нажмите «Исправить».' + : 'Сверхбыстрый сборщик пакетов из Arch User Repository', + style: TextStyle( + fontSize: 12, + color: (_componentStatus?.isParuBroken ?? false) + ? CupertinoColors.systemRed + : CupertinoColors.systemGrey, + ), ), ], ), @@ -471,7 +484,9 @@ class _SettingsScreenState extends State { await SetupWizardDialog.show(context); _loadSettings(); }, - child: Text((_componentStatus?.hasParu ?? false) ? 'Переустановить' : 'Установить Paru'), + child: Text((_componentStatus?.hasParu ?? false) + ? 'Переустановить' + : ((_componentStatus?.isParuBroken ?? false) ? 'Исправить Paru' : 'Установить Paru')), ), ], ), diff --git a/lib/src/ui/widgets/setup_wizard_dialog.dart b/lib/src/ui/widgets/setup_wizard_dialog.dart index bea9075..14e5963 100644 --- a/lib/src/ui/widgets/setup_wizard_dialog.dart +++ b/lib/src/ui/widgets/setup_wizard_dialog.dart @@ -256,16 +256,22 @@ class _SetupWizardDialogState extends State { icon: CupertinoIcons.flame_fill, iconColor: const Color(0xFFFF9500), title: 'AUR и Paru (Rust AUR Helper)', - subtitle: 'Сборка пакетов сообщества из Arch User Repository с максимальной скоростью', + subtitle: (_status?.isParuBroken ?? false) + ? '⚠️ Paru поврежден: не запускается из-за смены libalpm в Arch. Нажмите «Исправить»' + : 'Сборка пакетов сообщества из Arch User Repository с максимальной скоростью', statusText: (_status?.hasParu ?? false) ? 'Paru установлен (активен)' - : ((_status?.hasYay ?? false) ? 'Обнаружен yay (Paru не установлен)' : 'AUR-хелпер отсутствует'), - isInstalled: (_status?.hasParu ?? false) || (_status?.hasYay ?? false), + : ((_status?.isParuBroken ?? false) + ? 'Повреждён (libalpm)' + : ((_status?.hasYay ?? false) ? 'Обнаружен yay (Paru не установлен)' : 'AUR-хелпер отсутствует')), + isInstalled: _status?.hasParu ?? false, actionButton: PushButton( controlSize: ControlSize.small, secondary: _status?.hasParu ?? false, onPressed: _isInstalling ? null : _installParu, - child: Text((_status?.hasParu ?? false) ? 'Переустановить Paru' : 'Установить Paru'), + child: Text((_status?.hasParu ?? false) + ? 'Переустановить Paru' + : ((_status?.isParuBroken ?? false) ? 'Исправить Paru' : 'Установить Paru')), ), ),