fix: Detect broken Paru linked against old libalpm and repair via official repo/makepkg

This commit is contained in:
RarDog
2026-09-13 19:08:53 +03:00
parent 728a057242
commit fda87c63fd
4 changed files with 140 additions and 101 deletions
-12
View File
@@ -282,18 +282,6 @@ class CatalogService {
rating: 4.95, rating: 4.95,
votes: 1256, 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( CatalogApp(
id: 'com.usebottles.bottles', id: 'com.usebottles.bottles',
name: 'Bottles', name: 'Bottles',
+108 -78
View File
@@ -9,12 +9,16 @@ class SystemComponentStatus {
final bool hasFlathub; final bool hasFlathub;
final bool hasParu; final bool hasParu;
final bool hasYay; final bool hasYay;
final bool isParuBroken;
final String? paruBrokenReason;
const SystemComponentStatus({ const SystemComponentStatus({
required this.hasFlatpak, required this.hasFlatpak,
required this.hasFlathub, required this.hasFlathub,
required this.hasParu, required this.hasParu,
required this.hasYay, required this.hasYay,
this.isParuBroken = false,
this.paruBrokenReason,
}); });
bool get hasAurHelper => hasParu || hasYay; bool get hasAurHelper => hasParu || hasYay;
@@ -27,6 +31,8 @@ class ComponentInstallerService {
bool hasFlathub = false; bool hasFlathub = false;
bool hasParu = false; bool hasParu = false;
bool hasYay = false; bool hasYay = false;
bool isParuBroken = false;
String? paruBrokenReason;
try { try {
final res = await Process.run('which', ['flatpak']); final res = await Process.run('which', ['flatpak']);
@@ -41,8 +47,17 @@ class ComponentInstallerService {
} }
try { 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']); 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 (_) {} } catch (_) {}
try { try {
@@ -55,72 +70,53 @@ class ComponentInstallerService {
hasFlathub: hasFlathub, hasFlathub: hasFlathub,
hasParu: hasParu, hasParu: hasParu,
hasYay: hasYay, hasYay: hasYay,
isParuBroken: isParuBroken,
paruBrokenReason: paruBrokenReason,
); );
} }
/// Установка Flatpak и подключение репозитория Flathub /// Установка Flatpak и подключение репозитория Flathub
static Stream<String> installFlatpak(String? password) async* { static Stream<String> installFlatpak(String? password) async* {
yield '🚀 [Flatpak] Проверка и установка пакета flatpak через pacman...'; yield '🚀 [Flatpak] Начинаем установку Flatpak...';
// 1. Установка flatpak // 1. Установка пакета flatpak через pacman
final proc = await AdminService.runPrivileged( yield '📦 [Flatpak] Установка системного пакета flatpak...';
final pacmanProc = await AdminService.runPrivileged(
password, password,
'pacman', 'pacman',
['-S', '--needed', '--noconfirm', 'flatpak'], ['-S', '--needed', '--noconfirm', 'flatpak'],
); );
final controller = StreamController<String>(); final controller = StreamController<String>();
proc.stdout.transform(utf8.decoder).transform(const LineSplitter()).listen( pacmanProc.stdout.transform(utf8.decoder).transform(const LineSplitter()).listen((l) => controller.add(l));
(line) => controller.add(line), pacmanProc.stderr.transform(utf8.decoder).transform(const LineSplitter()).listen((l) => controller.add(l));
onError: (e) => controller.add('ERR: $e'), pacmanProc.exitCode.then((_) => Future.delayed(const Duration(milliseconds: 50), () => controller.close()));
);
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();
});
});
await for (final line in controller.stream) { await for (final line in controller.stream) {
yield line; yield line;
} }
final code = await proc.exitCode; final pacmanExit = await pacmanProc.exitCode;
if (code != 0) { if (pacmanExit != 0) {
yield '❌ [Flatpak] Ошибка установки пакета flatpak (код $code).'; yield '❌ [Flatpak] Ошибка установки pacman (код $pacmanExit)';
return; return;
} }
yield '✅ [Flatpak] Пакет flatpak успешно установлен в систему.'; yield '✅ [Flatpak] Пакет flatpak установлен!';
yield '🌐 [Flathub] Подключение официального репозитория Flathub...';
// 2. Добавление репозитория Flathub // 2. Подключение Flathub
try { yield '🌐 [Flathub] Подключение репозитория Flathub...';
final remoteProc = await Process.run('flatpak', [ final flathubRes = await Process.run('flatpak', [
'remote-add', 'remote-add',
'--if-not-exists', '--if-not-exists',
'flathub', 'flathub',
'https://dl.flathub.org/repo/flathub.flatpakrepo', 'https://dl.flathub.org/repo/flathub.flatpakrepo',
]); ]);
if (remoteProc.exitCode == 0) { if (flathubRes.exitCode == 0) {
yield '✅ [Flathub] Репозиторий Flathub успешно подключен!'; yield '✅ [Flathub] Репозиторий Flathub успешно добавлен!';
} else { } else {
// Попытка с правами суперпользователя yield '⚠️ [Flathub] Сообщение: ${flathubRes.stderr.toString().trim()}';
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';
} }
yield '🎉 [Успех] Интеграция Flatpak & Flathub полностью готова!'; yield '🎉 [Успех] Интеграция Flatpak & Flathub полностью готова!';
@@ -128,15 +124,59 @@ class ComponentInstallerService {
/// Установка AUR-хелпера Paru (Rust) /// Установка AUR-хелпера Paru (Rust)
static Stream<String> installParu(String? password) async* { static Stream<String> 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<String>();
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(); final status = await checkComponents();
if (status.hasYay) { 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( final proc = await Process.start(
'yay', 'yay',
['-S', '--noconfirm', 'paru-bin'], ['-S', '--noconfirm', 'paru'],
environment: {'LC_ALL': 'C'}, environment: {'LC_ALL': 'C'},
); );
@@ -162,38 +202,22 @@ class ComponentInstallerService {
final exitCode = await proc.exitCode; final exitCode = await proc.exitCode;
if (exitCode == 0) { if (exitCode == 0) {
AurService.resetCache(); final verify = await Process.run('paru', ['--version']);
yield '🎉 [Paru] Paru успешно установлен и готов к работе!'; if (verify.exitCode == 0) {
return;
} else {
yield '⚠️ [Paru] Не удалось установить через yay (код $exitCode), пробуем сборку из исходников paru...';
final procSrc = await Process.start(
'yay',
['-S', '--noconfirm', 'paru'],
environment: {'LC_ALL': 'C'},
);
final srcCtrl = StreamController<String>();
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) {
AurService.resetCache(); AurService.resetCache();
yield '🎉 [Paru] Paru успешно скомпилирован и установлен!'; yield '🎉 [Paru] Paru успешно скомпилирован и готов к работе (${verify.stdout.toString().trim()})!';
return; return;
} }
} }
yield '⚠️ [Paru] Установка через yay завершилась с кодом $exitCode, пробуем makepkg...';
} }
// Ручная установка paru-bin через makepkg // 3. Ручная сборка исходников paru через makepkg
yield '🛠️ [Paru] Проверка базовых инструментов сборки (base-devel, git)...'; yield '🛠️ [Paru] Проверка базовых инструментов сборки (base-devel, git, cargo)...';
final pacmanProc = await AdminService.runPrivileged( final pacmanProc = await AdminService.runPrivileged(
password, password,
'pacman', 'pacman',
['-S', '--needed', '--noconfirm', 'base-devel', 'git'], ['-S', '--needed', '--noconfirm', 'base-devel', 'git', 'cargo'],
); );
await pacmanProc.exitCode; await pacmanProc.exitCode;
@@ -205,12 +229,12 @@ class ComponentInstallerService {
Directory? tempDir; Directory? tempDir;
try { try {
tempDir = Directory.systemTemp.createTempSync('forge_paru_install_'); tempDir = Directory.systemTemp.createTempSync('forge_paru_install_');
yield '📥 [Paru] Клонирование paru-bin из AUR...'; yield '📥 [Paru] Клонирование исходников paru из AUR...';
final cloneRes = await Process.run('git', [ final cloneRes = await Process.run('git', [
'clone', 'clone',
'https://aur.archlinux.org/paru-bin.git', 'https://aur.archlinux.org/paru.git',
'${tempDir.path}/paru-bin', '${tempDir.path}/paru',
]); ]);
if (cloneRes.exitCode != 0) { if (cloneRes.exitCode != 0) {
@@ -218,11 +242,11 @@ class ComponentInstallerService {
return; return;
} }
yield '⚙️ [Paru] Сборка и установка пакета (makepkg -si)...'; yield '⚙️ [Paru] Сборка и установка пакета через makepkg -si...';
final makepkgProc = await Process.start( final makepkgProc = await Process.start(
'makepkg', 'makepkg',
['-si', '--noconfirm'], ['-si', '--noconfirm'],
workingDirectory: '${tempDir.path}/paru-bin', workingDirectory: '${tempDir.path}/paru',
environment: {'LC_ALL': 'C'}, environment: {'LC_ALL': 'C'},
); );
@@ -248,7 +272,13 @@ class ComponentInstallerService {
final exitCode = await makepkgProc.exitCode; final exitCode = await makepkgProc.exitCode;
if (exitCode == 0) { 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 { } else {
yield '❌ [Paru] makepkg завершился с кодом ошибки $exitCode.'; yield '❌ [Paru] makepkg завершился с кодом ошибки $exitCode.';
} }
+22 -7
View File
@@ -438,28 +438,41 @@ class _SettingsScreenState extends State<SettingsScreen> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: (_componentStatus?.hasParu ?? false) color: (_componentStatus?.hasParu ?? false)
? CupertinoColors.systemGreen.withValues(alpha: 0.15) ? 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), borderRadius: BorderRadius.circular(4),
), ),
child: Text( child: Text(
(_componentStatus?.hasParu ?? false) (_componentStatus?.hasParu ?? false)
? 'Paru активен' ? 'Paru активен'
: ((_componentStatus?.hasYay ?? false) ? 'Обнаружен yay' : 'Отсутствует'), : ((_componentStatus?.isParuBroken ?? false)
? 'Повреждён (libalpm)'
: ((_componentStatus?.hasYay ?? false) ? 'Обнаружен yay' : 'Отсутствует')),
style: TextStyle( style: TextStyle(
fontSize: 10.5, fontSize: 10.5,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: (_componentStatus?.hasParu ?? false) color: (_componentStatus?.hasParu ?? false)
? CupertinoColors.systemGreen ? CupertinoColors.systemGreen
: CupertinoColors.systemGrey, : ((_componentStatus?.isParuBroken ?? false)
? CupertinoColors.systemRed
: CupertinoColors.systemGrey),
), ),
), ),
), ),
], ],
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
const Text( Text(
'Сверхбыстрый сборщик пакетов из Arch User Repository', (_componentStatus?.isParuBroken ?? false)
style: TextStyle(fontSize: 12, color: CupertinoColors.systemGrey), ? '⚠️ 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<SettingsScreen> {
await SetupWizardDialog.show(context); await SetupWizardDialog.show(context);
_loadSettings(); _loadSettings();
}, },
child: Text((_componentStatus?.hasParu ?? false) ? 'Переустановить' : 'Установить Paru'), child: Text((_componentStatus?.hasParu ?? false)
? 'Переустановить'
: ((_componentStatus?.isParuBroken ?? false) ? 'Исправить Paru' : 'Установить Paru')),
), ),
], ],
), ),
+10 -4
View File
@@ -256,16 +256,22 @@ class _SetupWizardDialogState extends State<SetupWizardDialog> {
icon: CupertinoIcons.flame_fill, icon: CupertinoIcons.flame_fill,
iconColor: const Color(0xFFFF9500), iconColor: const Color(0xFFFF9500),
title: 'AUR и Paru (Rust AUR Helper)', title: 'AUR и Paru (Rust AUR Helper)',
subtitle: 'Сборка пакетов сообщества из Arch User Repository с максимальной скоростью', subtitle: (_status?.isParuBroken ?? false)
? '⚠️ Paru поврежден: не запускается из-за смены libalpm в Arch. Нажмите «Исправить»'
: 'Сборка пакетов сообщества из Arch User Repository с максимальной скоростью',
statusText: (_status?.hasParu ?? false) statusText: (_status?.hasParu ?? false)
? 'Paru установлен (активен)' ? 'Paru установлен (активен)'
: ((_status?.hasYay ?? false) ? 'Обнаружен yay (Paru не установлен)' : 'AUR-хелпер отсутствует'), : ((_status?.isParuBroken ?? false)
isInstalled: (_status?.hasParu ?? false) || (_status?.hasYay ?? false), ? 'Повреждён (libalpm)'
: ((_status?.hasYay ?? false) ? 'Обнаружен yay (Paru не установлен)' : 'AUR-хелпер отсутствует')),
isInstalled: _status?.hasParu ?? false,
actionButton: PushButton( actionButton: PushButton(
controlSize: ControlSize.small, controlSize: ControlSize.small,
secondary: _status?.hasParu ?? false, secondary: _status?.hasParu ?? false,
onPressed: _isInstalling ? null : _installParu, onPressed: _isInstalling ? null : _installParu,
child: Text((_status?.hasParu ?? false) ? 'Переустановить Paru' : 'Установить Paru'), child: Text((_status?.hasParu ?? false)
? 'Переустановить Paru'
: ((_status?.isParuBroken ?? false) ? 'Исправить Paru' : 'Установить Paru')),
), ),
), ),