fix: Detect broken Paru linked against old libalpm and repair via official repo/makepkg
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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', [
|
||||
'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<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) {
|
||||
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) {
|
||||
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.';
|
||||
}
|
||||
|
||||
@@ -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')),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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')),
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user