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,
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',
+101 -71
View File
@@ -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<String> 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<String>();
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', [
// 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 успешно подключен!';
if (flathubRes.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';
yield '⚠️ [Flathub] Сообщение: ${flathubRes.stderr.toString().trim()}';
}
yield '🎉 [Успех] Интеграция Flatpak & Flathub полностью готова!';
@@ -128,15 +124,59 @@ class ComponentInstallerService {
/// Установка AUR-хелпера Paru (Rust)
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();
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) {
final verify = await Process.run('paru', ['--version']);
if (verify.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<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();
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) {
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.';
}
+22 -7
View File
@@ -438,28 +438,41 @@ class _SettingsScreenState extends State<SettingsScreen> {
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<SettingsScreen> {
await SetupWizardDialog.show(context);
_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,
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')),
),
),