feat(patterns): full Linux-to-Linux distro matrix + M2 LLM chat wiring
Pattern files: 12 cross-family migration pairs covering debian, fedora, arch, opensuse — each tuned to the user's prior tooling (apt, dnf, pacman, zypper). Includes the custom linux-to-arch file for experienced distro-hoppers and the macos-to-arch / windows-to-debian expansions from the prior session. Code changes: - patterns::load() accepts source_distro_family: Option<&str> — tries specific debian-to-arch.toml before falling back to linux-to-arch.toml - MigrationConfig adds source_distro_family: Option<String> with serde default - complete_onboarding() accepts optional source_distro arg and derives family via distro_family() for Linux-to-Linux migrations - llm.rs: Ollama streaming client with Vec<u8> buffer for UTF-8 safety, emit errors logged not silenced - commands::chat: spawns stream task, returns immediately so frontend isn't blocked waiting for full LLM response - lib.rs: registers mod llm and commands::chat in invoke_handler
This commit is contained in:
parent
89271f08f8
commit
19286e9860
17 changed files with 1999 additions and 13 deletions
146
src-tauri/patterns/arch-to-debian.toml
Normal file
146
src-tauri/patterns/arch-to-debian.toml
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
[meta]
|
||||||
|
source_os = "linux"
|
||||||
|
target_distro_family = "debian"
|
||||||
|
|
||||||
|
# Arch/Manjaro/EndeavourOS user moving to Debian/Ubuntu/Mint.
|
||||||
|
# Body text assumes pacman, AUR, and rolling release familiarity.
|
||||||
|
|
||||||
|
[log_paths]
|
||||||
|
steam = "~/.local/share/Steam/logs/content_log.txt"
|
||||||
|
proton = "~/.local/share/Steam/logs/proton_log.txt"
|
||||||
|
|
||||||
|
# ── apt / dpkg ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "apt-lock"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Could not get lock /var/lib/dpkg/lock"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Package manager is locked"
|
||||||
|
body = "Another apt process is running — often unattended-upgrades (automatic background updates, no Arch equivalent). Wait a minute. If stuck: sudo rm /var/lib/dpkg/lock-frontend /var/lib/dpkg/lock && sudo dpkg --configure -a"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "dpkg-interrupted"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "dpkg was interrupted"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Package install was interrupted"
|
||||||
|
body = "Like a pacman transaction that got killed, but dpkg needs manual recovery. Fix: sudo dpkg --configure -a"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "apt-unmet-dependency"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Unmet dependencies"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Package dependency conflict"
|
||||||
|
body = "Unlike pacman which puts the conflict choice on you, apt tries to auto-resolve. Let it: sudo apt --fix-broken install — if it can't, read the message for which packages conflict."
|
||||||
|
|
||||||
|
# ── System ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "kernel-driver-firmware"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "firmware: failed to load"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Firmware file missing"
|
||||||
|
body = "On Debian: sudo apt install firmware-linux firmware-linux-nonfree (enable non-free sources first). On Ubuntu: sudo apt install linux-firmware. Unlike Arch's single linux-firmware package, Debian splits firmware by license."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "oom-killer"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Out of memory: Kill process"
|
||||||
|
severity = "warn"
|
||||||
|
title = "OOM killer fired"
|
||||||
|
body = "Debian stable has conservative defaults — Ubuntu enables zswap, Debian doesn't. If you used zram-generator on Arch, set up a swapfile here: fallocate -l 4G /swapfile && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "disk-io-error"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Buffer I/O error on device"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Disk I/O error"
|
||||||
|
body = "Check SMART: sudo smartctl -a /dev/sdX — install: sudo apt install smartmontools"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "locale-error"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "failed to set locale"
|
||||||
|
severity = "info"
|
||||||
|
title = "Locale configuration error"
|
||||||
|
body = "Debian generates locales via dpkg-reconfigure: sudo dpkg-reconfigure locales — select your locale in the curses UI. Unlike Arch where you edit /etc/locale.gen directly, Debian abstracts this."
|
||||||
|
|
||||||
|
# ── AppArmor ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "apparmor-denial"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "apparmor=\"DENIED\""
|
||||||
|
severity = "info"
|
||||||
|
title = "AppArmor access denied"
|
||||||
|
body = "Debian/Ubuntu ships AppArmor — Arch doesn't use MAC by default. An app is blocked by a security profile. Check: sudo aa-status — audit: sudo aa-logprof — or put the profile in complain mode: sudo aa-complain /etc/apparmor.d/<profile>"
|
||||||
|
|
||||||
|
# ── Audio ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pipewire-connect-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Failed to connect to PipeWire"
|
||||||
|
severity = "warn"
|
||||||
|
title = "PipeWire not responding"
|
||||||
|
body = "Ubuntu 22.04+/Debian 12+ ship PipeWire like Arch. Restart: systemctl --user restart pipewire pipewire-pulse wireplumber"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pulseaudio-connect-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Failed to connect to pulseaudio"
|
||||||
|
severity = "warn"
|
||||||
|
title = "PulseAudio not responding"
|
||||||
|
body = "Older Debian systems still use PulseAudio. Restart: pulseaudio --kill && pulseaudio --start — you can migrate to PipeWire: sudo apt install pipewire-audio"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "bluetooth-rfkill-blocked"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Blocked through rfkill"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Bluetooth rfkill blocked"
|
||||||
|
body = "rfkill unblock bluetooth — same as Arch."
|
||||||
|
|
||||||
|
# ── GPU / display ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "gpu-hang"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "GPU HANG"
|
||||||
|
severity = "warn"
|
||||||
|
title = "GPU hang"
|
||||||
|
body = "GPU stopped responding. For NVIDIA on Ubuntu: sudo apt install nvidia-driver-<version> or ubuntu-drivers autoinstall. Debian requires non-free sources: apt install nvidia-driver"
|
||||||
|
|
||||||
|
# ── Network ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "networkmanager-activation-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Activation failed"
|
||||||
|
severity = "info"
|
||||||
|
title = "NetworkManager: connection failed"
|
||||||
|
body = "nmcli device status — Debian minimal installs may use ifupdown instead of NetworkManager. Check: systemctl status NetworkManager — install if missing: sudo apt install network-manager"
|
||||||
|
|
||||||
|
# ── Printing ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "cups-server-error"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Unable to connect to CUPS server"
|
||||||
|
severity = "info"
|
||||||
|
title = "Printer service not running"
|
||||||
|
body = "sudo systemctl start cups && sudo systemctl enable cups — Debian/Ubuntu handle printing through CUPS; Arch also uses CUPS but it's not always enabled by default."
|
||||||
|
|
||||||
|
# ── Gaming ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "proton-runtime-missing"
|
||||||
|
sources = ["applog:proton"]
|
||||||
|
match_text = "wine: cannot find"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Proton runtime issue"
|
||||||
|
body = "Right-click game in Steam -> Properties -> Local Files -> Verify integrity."
|
||||||
136
src-tauri/patterns/arch-to-fedora.toml
Normal file
136
src-tauri/patterns/arch-to-fedora.toml
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
[meta]
|
||||||
|
source_os = "linux"
|
||||||
|
target_distro_family = "fedora"
|
||||||
|
|
||||||
|
# Arch/Manjaro/EndeavourOS user moving to Fedora.
|
||||||
|
# Body text assumes pacman, AUR, and rolling release familiarity.
|
||||||
|
|
||||||
|
[log_paths]
|
||||||
|
steam = "~/.local/share/Steam/logs/content_log.txt"
|
||||||
|
proton = "~/.local/share/Steam/logs/proton_log.txt"
|
||||||
|
|
||||||
|
# ── DNF / RPM ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "dnf-lock"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Another app is currently holding the dnf lock"
|
||||||
|
severity = "warn"
|
||||||
|
title = "DNF package manager is locked"
|
||||||
|
body = "dnf-automatic (Fedora's equivalent of Arch's unattended auto-updates, though Arch doesn't do auto-updates) is probably running. Wait it out or: sudo ps aux | grep dnf"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "dnf-dep-conflict"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "conflicts with"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Package dependency conflict"
|
||||||
|
body = "Unlike pacman where you resolve conflicts manually, dnf tries to auto-resolve. It usually succeeds. If not: sudo dnf distro-sync — the Fedora equivalent of pacman -Syu for bringing the system fully in sync."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "dnf-gpg-key"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "GPG key retrieval failed"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Repository GPG key missing"
|
||||||
|
body = "Import the key: sudo rpm --import /path/to/key.gpg — or re-run with: sudo dnf install --nogpgcheck (only if you trust the source). RPM Fusion keys are imported automatically when you enable the repo."
|
||||||
|
|
||||||
|
# ── SELinux (new concept for Arch users) ─────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "selinux-denial"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "type=AVC"
|
||||||
|
severity = "info"
|
||||||
|
title = "SELinux access denied"
|
||||||
|
body = "Fedora ships SELinux enforcing by default — there's nothing like this on Arch by default. A security policy is blocking an action. Check what's blocked: ausearch -m AVC -ts recent — get a fix suggestion: sealert -a /var/log/audit/audit.log — don't just set SELinux to permissive; that defeats the security model."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "selinux-context-wrong"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "restorecon"
|
||||||
|
severity = "info"
|
||||||
|
title = "SELinux file context mismatch"
|
||||||
|
body = "A file has the wrong security label — common when copying files from an Arch system or an external drive. Fix: sudo restorecon -Rv /path/to/file"
|
||||||
|
|
||||||
|
# ── System ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "kernel-driver-firmware"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "firmware: failed to load"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Firmware file missing"
|
||||||
|
body = "sudo dnf install linux-firmware — same coverage as Arch's linux-firmware package. Some chips may need RPM Fusion nonfree: sudo dnf install rpmfusion-nonfree-release-$(rpm -E %fedora)"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "oom-killer"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Out of memory: Kill process"
|
||||||
|
severity = "warn"
|
||||||
|
title = "OOM killer fired"
|
||||||
|
body = "A process was killed for RAM. Fedora enables zswap by default on modern releases. If you used zram on Arch, install zram-generator: sudo dnf install zram-generator"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "disk-io-error"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Buffer I/O error on device"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Disk I/O error"
|
||||||
|
body = "Check SMART: sudo smartctl -a /dev/sdX — install: sudo dnf install smartmontools"
|
||||||
|
|
||||||
|
# ── Audio ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pipewire-connect-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Failed to connect to PipeWire"
|
||||||
|
severity = "warn"
|
||||||
|
title = "PipeWire not responding"
|
||||||
|
body = "Both Arch and Fedora ship PipeWire. Restart: systemctl --user restart pipewire pipewire-pulse wireplumber"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "bluetooth-rfkill-blocked"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Blocked through rfkill"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Bluetooth rfkill blocked"
|
||||||
|
body = "rfkill unblock bluetooth — same as Arch."
|
||||||
|
|
||||||
|
# ── GPU / display ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "gpu-hang"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "GPU HANG"
|
||||||
|
severity = "warn"
|
||||||
|
title = "GPU hang"
|
||||||
|
body = "GPU stopped responding. For NVIDIA on Fedora, RPM Fusion is the right source: sudo dnf install akmod-nvidia — unlike Arch's nvidia-dkms from the official repos."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "xwayland-crash"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "XWayland server terminated unexpectedly"
|
||||||
|
severity = "warn"
|
||||||
|
title = "XWayland crashed"
|
||||||
|
body = "X11 apps dead until session restart. Fedora GNOME defaults to Wayland like Arch KDE/GNOME can."
|
||||||
|
|
||||||
|
# ── Network ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "networkmanager-activation-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Activation failed"
|
||||||
|
severity = "info"
|
||||||
|
title = "NetworkManager: connection failed"
|
||||||
|
body = "nmcli device status — same NetworkManager as Arch. Firmware issues: sudo dmesg | grep firmware"
|
||||||
|
|
||||||
|
# ── Gaming ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "proton-runtime-missing"
|
||||||
|
sources = ["applog:proton"]
|
||||||
|
match_text = "wine: cannot find"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Proton runtime issue"
|
||||||
|
body = "Right-click game in Steam -> Properties -> Local Files -> Verify integrity. Steam on Fedora: sudo dnf install steam (from RPM Fusion free)."
|
||||||
130
src-tauri/patterns/arch-to-opensuse.toml
Normal file
130
src-tauri/patterns/arch-to-opensuse.toml
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
[meta]
|
||||||
|
source_os = "linux"
|
||||||
|
target_distro_family = "opensuse"
|
||||||
|
|
||||||
|
# Arch/Manjaro/EndeavourOS user moving to openSUSE Tumbleweed or Leap.
|
||||||
|
# Body text assumes pacman, AUR, and rolling release familiarity.
|
||||||
|
|
||||||
|
[log_paths]
|
||||||
|
steam = "~/.local/share/Steam/logs/content_log.txt"
|
||||||
|
proton = "~/.local/share/Steam/logs/proton_log.txt"
|
||||||
|
|
||||||
|
# ── zypper / RPM ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "zypper-lock"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "System management is locked"
|
||||||
|
severity = "warn"
|
||||||
|
title = "zypper package manager is locked"
|
||||||
|
body = "Another zypper or PackageKit (GNOME Software) process is running. Wait it out or check: sudo ps aux | grep zypper"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "zypper-dep-conflict"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "conflicts with"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Package dependency conflict"
|
||||||
|
body = "Unlike pacman which puts the conflict on you immediately, zypper presents resolution options. zypper dup (distribution upgrade) is more aggressive than zypper up and usually resolves what zypper up won't."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "zypper-gpg-key"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "does not verify"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Repository signature not trusted"
|
||||||
|
body = "Auto-import keys: sudo zypper --gpg-auto-import-keys ref — unlike pacman-key, zypper manages repo keys through RPM's keyring."
|
||||||
|
|
||||||
|
# ── AppArmor ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "apparmor-denial"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "apparmor=\"DENIED\""
|
||||||
|
severity = "info"
|
||||||
|
title = "AppArmor access denied"
|
||||||
|
body = "openSUSE ships AppArmor — Arch doesn't use MAC by default. An app is blocked by a profile. Check: sudo aa-status — audit and fix: sudo aa-logprof — or set the profile to complain mode: sudo aa-complain <profile-name>"
|
||||||
|
|
||||||
|
# ── YaST ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "yast-backend-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "YaST got signal"
|
||||||
|
severity = "warn"
|
||||||
|
title = "YaST configuration tool crashed"
|
||||||
|
body = "YaST is openSUSE's graphical admin tool — no Arch equivalent. If it crashed mid-operation: sudo yast2 in a terminal to run the text-mode version, which is more stable for recovery."
|
||||||
|
|
||||||
|
# ── System ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "kernel-driver-firmware"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "firmware: failed to load"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Firmware file missing"
|
||||||
|
body = "sudo zypper install kernel-firmware — similar to Arch's linux-firmware in scope."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "oom-killer"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Out of memory: Kill process"
|
||||||
|
severity = "warn"
|
||||||
|
title = "OOM killer fired"
|
||||||
|
body = "A process was killed for RAM. If you used zram-generator on Arch, openSUSE supports it too: sudo zypper install zram-generator — or set up swap via YaST -> System -> Partitioner."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "disk-io-error"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Buffer I/O error on device"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Disk I/O error"
|
||||||
|
body = "Check SMART: sudo smartctl -a /dev/sdX — install: sudo zypper install smartmontools"
|
||||||
|
|
||||||
|
# ── Audio ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pipewire-connect-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Failed to connect to PipeWire"
|
||||||
|
severity = "warn"
|
||||||
|
title = "PipeWire not responding"
|
||||||
|
body = "Tumbleweed ships PipeWire like Arch. Restart: systemctl --user restart pipewire pipewire-pulse wireplumber"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "bluetooth-rfkill-blocked"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Blocked through rfkill"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Bluetooth rfkill blocked"
|
||||||
|
body = "rfkill unblock bluetooth — same as Arch."
|
||||||
|
|
||||||
|
# ── GPU / display ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "gpu-hang"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "GPU HANG"
|
||||||
|
severity = "warn"
|
||||||
|
title = "GPU hang"
|
||||||
|
body = "GPU stopped responding. For NVIDIA on openSUSE: use the NVIDIA OBS repo (similar to Arch's nvidia-dkms from official repos). AMD users: mesa is in the default repos."
|
||||||
|
|
||||||
|
# ── Network ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "networkmanager-activation-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Activation failed"
|
||||||
|
severity = "info"
|
||||||
|
title = "NetworkManager: connection failed"
|
||||||
|
body = "nmcli device status — openSUSE may use Wicked on server installs instead of NetworkManager. Check: systemctl status NetworkManager wicked"
|
||||||
|
|
||||||
|
# ── Gaming ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "proton-runtime-missing"
|
||||||
|
sources = ["applog:proton"]
|
||||||
|
match_text = "wine: cannot find"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Proton runtime issue"
|
||||||
|
body = "Right-click game in Steam -> Properties -> Local Files -> Verify integrity. Steam on openSUSE: sudo zypper install steam (from the games repo on OBS)."
|
||||||
186
src-tauri/patterns/debian-to-arch.toml
Normal file
186
src-tauri/patterns/debian-to-arch.toml
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
[meta]
|
||||||
|
source_os = "linux"
|
||||||
|
target_distro_family = "arch"
|
||||||
|
|
||||||
|
# Debian/Ubuntu/Mint user on their first Arch install.
|
||||||
|
# Body text assumes comfort with apt and systemd but not AUR or rolling release.
|
||||||
|
|
||||||
|
[log_paths]
|
||||||
|
pacman = "/var/log/pacman.log"
|
||||||
|
steam = "~/.local/share/Steam/logs/content_log.txt"
|
||||||
|
proton = "~/.local/share/Steam/logs/proton_log.txt"
|
||||||
|
lutris = "~/.cache/lutris/logs/lutris.log"
|
||||||
|
|
||||||
|
# ── pacman / AUR ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pacman-db-lock"
|
||||||
|
sources = ["journald", "applog:pacman"]
|
||||||
|
match_text = "could not lock database: File exists"
|
||||||
|
severity = "warn"
|
||||||
|
title = "pacman database locked"
|
||||||
|
body = "Lock file left from a crashed pacman run — like dpkg getting killed mid-install. If nothing is running: sudo rm /var/lib/pacman/db.lck — verify first: fuser /var/lib/pacman/db.lck"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pacman-dep-conflict"
|
||||||
|
sources = ["journald", "applog:pacman"]
|
||||||
|
match_text = "conflicting dependencies"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Dependency conflict"
|
||||||
|
body = "Unlike apt, pacman won't auto-resolve conflicts — you decide. Usually one package replaces another (e.g. pipewire-pulse replaces pulseaudio). Remove the conflicting package first, then retry."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pacman-conflicting-files"
|
||||||
|
sources = ["journald", "applog:pacman"]
|
||||||
|
match_text = "error: failed to commit transaction (conflicting files)"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Conflicting files on install"
|
||||||
|
body = "A file already exists on disk that the package wants to own. Check which package owns it: pacman -Qo /path/to/file — then remove the stale file or use --overwrite if you're sure it's safe."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "partial-upgrade-warning"
|
||||||
|
sources = ["applog:pacman"]
|
||||||
|
match_text = "warning: database file for"
|
||||||
|
severity = "info"
|
||||||
|
title = "Package database out of sync"
|
||||||
|
body = "On Arch, pacman -Sy (sync without upgrade) is dangerous — partial upgrades break the system. Unlike apt where partial syncs are fine, on Arch always use pacman -Syu. This is the biggest rule to learn coming from Debian."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "aur-build-failure"
|
||||||
|
sources = ["journald", "applog:pacman"]
|
||||||
|
match_text = "error: failed to build"
|
||||||
|
severity = "warn"
|
||||||
|
title = "AUR package build failed"
|
||||||
|
body = "The AUR is source-based — no binary packages, makepkg compiles from a PKGBUILD. Read the full output for the cause: missing makedepend, broken upstream URL, or bad patch. Check the package's AUR comments page for known fixes."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "aur-pgp-key"
|
||||||
|
sources = ["journald", "applog:pacman"]
|
||||||
|
match_text = "unknown public key"
|
||||||
|
severity = "warn"
|
||||||
|
title = "PGP key not in keyring"
|
||||||
|
body = "AUR packages verify signatures — different from apt's keyring model. Import the key: gpg --recv-keys <keyid> — or if the PKGBUILD lists validpgpkeys, import exactly those."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "makepkg-missing-deps"
|
||||||
|
sources = ["journald", "applog:pacman"]
|
||||||
|
match_text = "Missing dependencies"
|
||||||
|
severity = "warn"
|
||||||
|
title = "AUR build dependencies missing"
|
||||||
|
body = "AUR helpers like paru/yay resolve makedepends automatically. If building manually: sudo pacman -S <dep> first. This is like build-dep in apt but you have to do it manually with plain makepkg."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "chaotic-aur-sig-fail"
|
||||||
|
sources = ["journald", "applog:pacman"]
|
||||||
|
match_text = "signature from"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Package signature verification failed"
|
||||||
|
body = "Chaotic-AUR key not trusted. Import it: sudo pacman-key --recv-key 3056513887B78AEB --keyserver keyserver.ubuntu.com && sudo pacman-key --lsign-key 3056513887B78AEB — then retry."
|
||||||
|
|
||||||
|
# ── Kernel / DKMS ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "dkms-build-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Error! Build of"
|
||||||
|
severity = "warn"
|
||||||
|
title = "DKMS module failed to build"
|
||||||
|
body = "A kernel module didn't compile after a kernel update. On Arch's rolling release this happens more than on Debian stable. Check: dkms status — then reinstall the dkms package or wait for an AUR update. Debian had linux-headers; here it's linux-headers (or linux-cachyos-headers on CachyOS)."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "kernel-driver-firmware"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "firmware: failed to load"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Firmware file missing"
|
||||||
|
body = "sudo pacman -S linux-firmware — similar to firmware-linux-nonfree on Debian but one package covers most hardware. Specific chips (Realtek wifi) may need AUR packages."
|
||||||
|
|
||||||
|
# ── System ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "locale-not-set"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Cannot set LC_ALL to default locale"
|
||||||
|
severity = "info"
|
||||||
|
title = "Locale not generated"
|
||||||
|
body = "Unlike Debian, Arch doesn't pre-generate locales. Edit /etc/locale.gen (uncomment your locale, e.g. en_US.UTF-8), then: sudo locale-gen — and set LANG=en_US.UTF-8 in /etc/locale.conf."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "oom-killer"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Out of memory: Kill process"
|
||||||
|
severity = "warn"
|
||||||
|
title = "OOM killer fired"
|
||||||
|
body = "A process was killed for RAM. Arch doesn't enable zswap by default like Ubuntu does. Add zram: sudo pacman -S zram-generator — or add a swapfile."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "disk-io-error"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Buffer I/O error on device"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Disk I/O error"
|
||||||
|
body = "Storage error on a block device. Check SMART: sudo smartctl -a /dev/sdX — or for NVMe: sudo nvme smart-log /dev/nvme0."
|
||||||
|
|
||||||
|
# ── Audio ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pipewire-connect-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Failed to connect to PipeWire"
|
||||||
|
severity = "warn"
|
||||||
|
title = "PipeWire not responding"
|
||||||
|
body = "systemctl --user restart pipewire pipewire-pulse wireplumber — Arch defaults to PipeWire; no PulseAudio fallback like Ubuntu has."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "bluetooth-rfkill-blocked"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Blocked through rfkill"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Bluetooth rfkill blocked"
|
||||||
|
body = "rfkill unblock bluetooth — if hard-blocked, check BIOS or a physical switch."
|
||||||
|
|
||||||
|
# ── GPU / display ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "gpu-hang"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "GPU HANG"
|
||||||
|
severity = "warn"
|
||||||
|
title = "GPU hang"
|
||||||
|
body = "GPU stopped responding. On Arch with AMD: check mesa version vs kernel version — both roll together. On NVIDIA: check nvidia-dkms matches the running kernel."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "xwayland-crash"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "XWayland server terminated unexpectedly"
|
||||||
|
severity = "warn"
|
||||||
|
title = "XWayland crashed"
|
||||||
|
body = "X11 apps will be dead until session restart. If reproducible with a specific app, check for a Wayland-native version or force X11 with WAYLAND_DISPLAY= unset."
|
||||||
|
|
||||||
|
# ── Network ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "networkmanager-activation-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Activation failed"
|
||||||
|
severity = "info"
|
||||||
|
title = "NetworkManager: connection failed"
|
||||||
|
body = "nmcli device status — if a wifi adapter is missing, check dmesg for firmware errors. Arch ships most firmware in linux-firmware; some Realtek chips need AUR packages."
|
||||||
|
|
||||||
|
# ── Gaming ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "proton-runtime-missing"
|
||||||
|
sources = ["applog:proton"]
|
||||||
|
match_text = "wine: cannot find"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Proton runtime issue"
|
||||||
|
body = "Right-click game in Steam -> Properties -> Local Files -> Verify integrity."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "lutris-wine-fail"
|
||||||
|
sources = ["applog:lutris"]
|
||||||
|
match_text = "Wine is not installed"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Lutris: Wine not found"
|
||||||
|
body = "Lutris needs a Wine runner. In Lutris: Preferences -> Runners -> Wine -> Install — or: paru -S wine-staging"
|
||||||
144
src-tauri/patterns/debian-to-fedora.toml
Normal file
144
src-tauri/patterns/debian-to-fedora.toml
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
[meta]
|
||||||
|
source_os = "linux"
|
||||||
|
target_distro_family = "fedora"
|
||||||
|
|
||||||
|
# Debian/Ubuntu/Mint user on their first Fedora/RHEL/CentOS install.
|
||||||
|
# Body text assumes apt/dpkg familiarity, no RPM experience.
|
||||||
|
|
||||||
|
[log_paths]
|
||||||
|
steam = "~/.local/share/Steam/logs/content_log.txt"
|
||||||
|
proton = "~/.local/share/Steam/logs/proton_log.txt"
|
||||||
|
|
||||||
|
# ── DNF / RPM ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "dnf-lock"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Another app is currently holding the dnf lock"
|
||||||
|
severity = "warn"
|
||||||
|
title = "DNF package manager is locked"
|
||||||
|
body = "Another dnf process is running — like apt being held by unattended-upgrades. Wait for it to finish or check: sudo ps aux | grep dnf — then kill if stuck."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "rpm-db-corrupt"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "rpmdb"
|
||||||
|
severity = "warn"
|
||||||
|
title = "RPM database issue"
|
||||||
|
body = "Like a corrupted dpkg database. Rebuild it: sudo rpm --rebuilddb — then retry your dnf command."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "dnf-dep-conflict"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "conflicts with"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Package dependency conflict"
|
||||||
|
body = "DNF can auto-resolve some conflicts but not all. Read the conflict message — usually one package provides what another needs. Try: sudo dnf distro-sync to bring everything in sync."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "dnf-gpg-key"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "GPG key retrieval failed"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Repository GPG key missing"
|
||||||
|
body = "A repo's signing key isn't trusted. Import it: sudo rpm --import /path/to/key.gpg — or re-enable the repo's key: sudo dnf repoinfo <repo>"
|
||||||
|
|
||||||
|
# ── SELinux ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "selinux-denial"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "type=AVC"
|
||||||
|
severity = "info"
|
||||||
|
title = "SELinux access denied"
|
||||||
|
body = "Fedora ships SELinux enforcing by default — there's nothing like this on Debian/Ubuntu. This is a security policy denial, not a bug. Check: ausearch -m AVC -ts recent — then run: sealert -a /var/log/audit/audit.log for a fix suggestion."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "selinux-context-wrong"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "restorecon"
|
||||||
|
severity = "info"
|
||||||
|
title = "SELinux file context mismatch"
|
||||||
|
body = "A file has the wrong security label. Fix: sudo restorecon -Rv /path/to/file — common after copying files from Debian or moving data between filesystems."
|
||||||
|
|
||||||
|
# ── System ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "kernel-driver-firmware"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "firmware: failed to load"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Firmware file missing"
|
||||||
|
body = "On Fedora: sudo dnf install linux-firmware — or for specific hardware check RPM Fusion's nonfree repo. Enable it: sudo dnf install https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "oom-killer"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Out of memory: Kill process"
|
||||||
|
severity = "warn"
|
||||||
|
title = "OOM killer fired"
|
||||||
|
body = "A process was killed for RAM. Fedora enables zswap by default on modern releases, but adding a swapfile helps on low-RAM systems: sudo systemctl enable --now systemd-swap"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "disk-io-error"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Buffer I/O error on device"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Disk I/O error"
|
||||||
|
body = "Storage error. Check SMART: sudo smartctl -a /dev/sdX — smartmontools is in the default Fedora repos."
|
||||||
|
|
||||||
|
# ── Audio ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pipewire-connect-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Failed to connect to PipeWire"
|
||||||
|
severity = "warn"
|
||||||
|
title = "PipeWire not responding"
|
||||||
|
body = "Fedora pioneered PipeWire adoption — it's the default. Restart: systemctl --user restart pipewire pipewire-pulse wireplumber"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "bluetooth-rfkill-blocked"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Blocked through rfkill"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Bluetooth rfkill blocked"
|
||||||
|
body = "rfkill unblock bluetooth — if hard-blocked, check BIOS or a physical switch."
|
||||||
|
|
||||||
|
# ── GPU / display ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "gpu-hang"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "GPU HANG"
|
||||||
|
severity = "warn"
|
||||||
|
title = "GPU hang"
|
||||||
|
body = "GPU stopped responding. Check: dnf list installed | grep -i nvidia (or mesa) — RPM Fusion is the right source for proprietary NVIDIA drivers on Fedora."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "xwayland-crash"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "XWayland server terminated unexpectedly"
|
||||||
|
severity = "warn"
|
||||||
|
title = "XWayland crashed"
|
||||||
|
body = "Fedora ships GNOME on Wayland by default; XWayland handles X11 apps. Restart your session to recover X11 apps."
|
||||||
|
|
||||||
|
# ── Network ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "networkmanager-activation-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Activation failed"
|
||||||
|
severity = "info"
|
||||||
|
title = "NetworkManager: connection failed"
|
||||||
|
body = "nmcli device status — Fedora ships NetworkManager by default, same as Ubuntu. If a wifi adapter is missing, check: dmesg | grep firmware"
|
||||||
|
|
||||||
|
# ── Gaming ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "proton-runtime-missing"
|
||||||
|
sources = ["applog:proton"]
|
||||||
|
match_text = "wine: cannot find"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Proton runtime issue"
|
||||||
|
body = "Right-click game in Steam -> Properties -> Local Files -> Verify integrity. Steam on Fedora may also need: sudo dnf install steam (from RPM Fusion free repo)."
|
||||||
120
src-tauri/patterns/debian-to-opensuse.toml
Normal file
120
src-tauri/patterns/debian-to-opensuse.toml
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
[meta]
|
||||||
|
source_os = "linux"
|
||||||
|
target_distro_family = "opensuse"
|
||||||
|
|
||||||
|
# Debian/Ubuntu/Mint user on their first openSUSE Tumbleweed or Leap install.
|
||||||
|
# Body text assumes apt/dpkg familiarity; explains zypper and YaST concepts.
|
||||||
|
|
||||||
|
[log_paths]
|
||||||
|
steam = "~/.local/share/Steam/logs/content_log.txt"
|
||||||
|
proton = "~/.local/share/Steam/logs/proton_log.txt"
|
||||||
|
|
||||||
|
# ── zypper / RPM ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "zypper-lock"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "System management is locked"
|
||||||
|
severity = "warn"
|
||||||
|
title = "zypper package manager is locked"
|
||||||
|
body = "Another zypper or PackageKit process is running — like apt being held by unattended-upgrades. Wait it out or check: sudo ps aux | grep zypper — the lock file is at /var/run/zypp.pid"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "zypper-dep-conflict"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "conflicts with"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Package dependency conflict"
|
||||||
|
body = "zypper presents conflict resolution choices interactively. If running non-interactively, read the error — usually one package needs to be removed or a different provider selected. zypper dup (distribution upgrade) resolves more aggressively than zypper up."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "zypper-gpg-key"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "does not verify"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Repository signature not trusted"
|
||||||
|
body = "A repo key isn't trusted. Accept it: sudo zypper --gpg-auto-import-keys ref — or import manually: sudo rpm --import /path/to/key.gpg"
|
||||||
|
|
||||||
|
# ── AppArmor ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "apparmor-denial"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "apparmor=\"DENIED\""
|
||||||
|
severity = "info"
|
||||||
|
title = "AppArmor access denied"
|
||||||
|
body = "openSUSE ships AppArmor (similar to Ubuntu, not Debian default). An app is blocked by its security profile. Check: sudo aa-status — then audit the profile with: sudo aa-logprof"
|
||||||
|
|
||||||
|
# ── System ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "kernel-driver-firmware"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "firmware: failed to load"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Firmware file missing"
|
||||||
|
body = "sudo zypper install kernel-firmware — openSUSE packages firmware separately like Debian but the package is called kernel-firmware, not firmware-linux."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "oom-killer"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Out of memory: Kill process"
|
||||||
|
severity = "warn"
|
||||||
|
title = "OOM killer fired"
|
||||||
|
body = "A process was killed for RAM. openSUSE sets up swap during install; if you skipped it, add a swapfile via YaST -> System -> Partitioner or manually with dd + mkswap."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "disk-io-error"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Buffer I/O error on device"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Disk I/O error"
|
||||||
|
body = "Storage error. Check SMART: sudo smartctl -a /dev/sdX — install smartmontools first: sudo zypper install smartmontools"
|
||||||
|
|
||||||
|
# ── Audio ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pipewire-connect-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Failed to connect to PipeWire"
|
||||||
|
severity = "warn"
|
||||||
|
title = "PipeWire not responding"
|
||||||
|
body = "Tumbleweed ships PipeWire by default. Restart: systemctl --user restart pipewire pipewire-pulse wireplumber"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "bluetooth-rfkill-blocked"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Blocked through rfkill"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Bluetooth rfkill blocked"
|
||||||
|
body = "rfkill unblock bluetooth — if hard-blocked, check BIOS or a physical switch."
|
||||||
|
|
||||||
|
# ── GPU / display ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "gpu-hang"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "GPU HANG"
|
||||||
|
severity = "warn"
|
||||||
|
title = "GPU hang"
|
||||||
|
body = "GPU stopped responding. For NVIDIA on openSUSE, use the official NVIDIA repo: https://www.nvidia.com/object/unix.html — or the community packages.opensuse.org repo."
|
||||||
|
|
||||||
|
# ── Network ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "networkmanager-activation-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Activation failed"
|
||||||
|
severity = "info"
|
||||||
|
title = "NetworkManager: connection failed"
|
||||||
|
body = "nmcli device status — openSUSE uses NetworkManager by default. For wifi firmware issues: sudo zypper install kernel-firmware-iwlwifi (Intel) or kernel-firmware-realtek (Realtek)."
|
||||||
|
|
||||||
|
# ── Gaming ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "proton-runtime-missing"
|
||||||
|
sources = ["applog:proton"]
|
||||||
|
match_text = "wine: cannot find"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Proton runtime issue"
|
||||||
|
body = "Right-click game in Steam -> Properties -> Local Files -> Verify integrity. Steam on openSUSE: sudo zypper install steam (from the games repo on OBS)."
|
||||||
172
src-tauri/patterns/fedora-to-arch.toml
Normal file
172
src-tauri/patterns/fedora-to-arch.toml
Normal file
|
|
@ -0,0 +1,172 @@
|
||||||
|
[meta]
|
||||||
|
source_os = "linux"
|
||||||
|
target_distro_family = "arch"
|
||||||
|
|
||||||
|
# Fedora/RHEL/CentOS user on their first Arch install.
|
||||||
|
# Body text assumes DNF, SELinux, and RPM Fusion familiarity.
|
||||||
|
|
||||||
|
[log_paths]
|
||||||
|
pacman = "/var/log/pacman.log"
|
||||||
|
steam = "~/.local/share/Steam/logs/content_log.txt"
|
||||||
|
proton = "~/.local/share/Steam/logs/proton_log.txt"
|
||||||
|
lutris = "~/.cache/lutris/logs/lutris.log"
|
||||||
|
|
||||||
|
# ── pacman / AUR ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pacman-db-lock"
|
||||||
|
sources = ["journald", "applog:pacman"]
|
||||||
|
match_text = "could not lock database: File exists"
|
||||||
|
severity = "warn"
|
||||||
|
title = "pacman database locked"
|
||||||
|
body = "Lock file left from a crashed pacman run — like a dnf transaction that got killed. Remove if nothing is running: sudo rm /var/lib/pacman/db.lck — check first: fuser /var/lib/pacman/db.lck"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "partial-upgrade-warning"
|
||||||
|
sources = ["applog:pacman"]
|
||||||
|
match_text = "warning: database file for"
|
||||||
|
severity = "info"
|
||||||
|
title = "Package database out of sync"
|
||||||
|
body = "Arch rule #1 coming from Fedora: never run pacman -Sy (sync only). On Fedora, dnf check-update is safe; on Arch, syncing the database without upgrading breaks the system. Always: pacman -Syu"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pacman-dep-conflict"
|
||||||
|
sources = ["journald", "applog:pacman"]
|
||||||
|
match_text = "conflicting dependencies"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Dependency conflict"
|
||||||
|
body = "Unlike dnf which auto-resolves most conflicts, pacman puts the choice on you. Read the conflict — typically one package is being replaced (e.g. pipewire-pulse replaces pulseaudio). Remove the old one first."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "aur-build-failure"
|
||||||
|
sources = ["journald", "applog:pacman"]
|
||||||
|
match_text = "error: failed to build"
|
||||||
|
severity = "warn"
|
||||||
|
title = "AUR package build failed"
|
||||||
|
body = "The AUR is source-only — no binary RPM equivalent. makepkg compiles from a PKGBUILD. Read the build output; check the package's AUR comments page. paru/yay handle makedepends automatically."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "aur-pgp-key"
|
||||||
|
sources = ["journald", "applog:pacman"]
|
||||||
|
match_text = "unknown public key"
|
||||||
|
severity = "warn"
|
||||||
|
title = "PGP key not in keyring"
|
||||||
|
body = "Import the key: gpg --recv-keys <keyid> — different from RPM's --import; this is GnuPG's own keyring used by makepkg."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "chaotic-aur-sig-fail"
|
||||||
|
sources = ["journald", "applog:pacman"]
|
||||||
|
match_text = "signature from"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Package signature verification failed"
|
||||||
|
body = "Chaotic-AUR key not trusted. Import: sudo pacman-key --recv-key 3056513887B78AEB --keyserver keyserver.ubuntu.com && sudo pacman-key --lsign-key 3056513887B78AEB"
|
||||||
|
|
||||||
|
# ── SELinux → no SELinux ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "selinux-remnant"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "type=AVC"
|
||||||
|
severity = "info"
|
||||||
|
title = "SELinux audit entry (ignored on Arch)"
|
||||||
|
body = "Arch doesn't ship SELinux by default — if you see this, you may have carried over a journal from a Fedora partition, or installed selinux-utils manually. No action needed unless you intentionally set up SELinux on Arch."
|
||||||
|
|
||||||
|
# ── Kernel / DKMS ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "dkms-build-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Error! Build of"
|
||||||
|
severity = "warn"
|
||||||
|
title = "DKMS module failed to build"
|
||||||
|
body = "A kernel module didn't compile after a kernel update. More frequent on Arch's rolling kernel than on Fedora's slower cadence. Check: dkms status — reinstall the failing module package."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "kernel-driver-firmware"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "firmware: failed to load"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Firmware file missing"
|
||||||
|
body = "sudo pacman -S linux-firmware — covers most hardware. Some chips need AUR packages (similar to RPM Fusion nonfree on Fedora)."
|
||||||
|
|
||||||
|
# ── System ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "locale-not-set"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Cannot set LC_ALL to default locale"
|
||||||
|
severity = "info"
|
||||||
|
title = "Locale not generated"
|
||||||
|
body = "Unlike Fedora where locale is configured in the installer, Arch requires manual setup. Edit /etc/locale.gen, uncomment your locale, run: sudo locale-gen — then set LANG in /etc/locale.conf."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "oom-killer"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Out of memory: Kill process"
|
||||||
|
severity = "warn"
|
||||||
|
title = "OOM killer fired"
|
||||||
|
body = "A process was killed for RAM. Fedora enables zswap by default; Arch doesn't. Add zram: sudo pacman -S zram-generator"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "disk-io-error"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Buffer I/O error on device"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Disk I/O error"
|
||||||
|
body = "Check SMART: sudo smartctl -a /dev/sdX — install smartmontools: sudo pacman -S smartmontools"
|
||||||
|
|
||||||
|
# ── Audio ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pipewire-connect-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Failed to connect to PipeWire"
|
||||||
|
severity = "warn"
|
||||||
|
title = "PipeWire not responding"
|
||||||
|
body = "Both Fedora and Arch ship PipeWire by default. Restart: systemctl --user restart pipewire pipewire-pulse wireplumber"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "bluetooth-rfkill-blocked"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Blocked through rfkill"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Bluetooth rfkill blocked"
|
||||||
|
body = "rfkill unblock bluetooth — check: rfkill list to distinguish hard vs soft block."
|
||||||
|
|
||||||
|
# ── GPU / display ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "gpu-hang"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "GPU HANG"
|
||||||
|
severity = "warn"
|
||||||
|
title = "GPU hang"
|
||||||
|
body = "GPU stopped responding. On Arch, AMD uses mesa (pacman -S mesa); NVIDIA uses nvidia-dkms. Unlike Fedora where RPM Fusion handles NVIDIA, on Arch install from the official repos."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "xwayland-crash"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "XWayland server terminated unexpectedly"
|
||||||
|
severity = "warn"
|
||||||
|
title = "XWayland crashed"
|
||||||
|
body = "X11 apps dead until session restart. Fedora GNOME also defaults to Wayland; the behavior is the same."
|
||||||
|
|
||||||
|
# ── Network ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "networkmanager-activation-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Activation failed"
|
||||||
|
severity = "info"
|
||||||
|
title = "NetworkManager: connection failed"
|
||||||
|
body = "nmcli device status — same NetworkManager as Fedora. If a wifi adapter is missing, check dmesg for firmware errors."
|
||||||
|
|
||||||
|
# ── Gaming ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "proton-runtime-missing"
|
||||||
|
sources = ["applog:proton"]
|
||||||
|
match_text = "wine: cannot find"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Proton runtime issue"
|
||||||
|
body = "Right-click game in Steam -> Properties -> Local Files -> Verify integrity."
|
||||||
138
src-tauri/patterns/fedora-to-debian.toml
Normal file
138
src-tauri/patterns/fedora-to-debian.toml
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
[meta]
|
||||||
|
source_os = "linux"
|
||||||
|
target_distro_family = "debian"
|
||||||
|
|
||||||
|
# Fedora/RHEL user moving to Debian/Ubuntu/Mint.
|
||||||
|
# Body text assumes DNF, SELinux, and systemd familiarity.
|
||||||
|
|
||||||
|
[log_paths]
|
||||||
|
steam = "~/.local/share/Steam/logs/content_log.txt"
|
||||||
|
proton = "~/.local/share/Steam/logs/proton_log.txt"
|
||||||
|
|
||||||
|
# ── apt / dpkg ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "apt-lock"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Could not get lock /var/lib/dpkg/lock"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Package manager is locked"
|
||||||
|
body = "Another apt process is running — often unattended-upgrades in the background. Wait a minute. If stuck: sudo rm /var/lib/dpkg/lock-frontend /var/lib/dpkg/lock — then: sudo dpkg --configure -a"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "dpkg-interrupted"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "dpkg was interrupted"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Package install was interrupted"
|
||||||
|
body = "A previous install didn't finish cleanly. Fix: sudo dpkg --configure -a — like an interrupted dnf transaction, but requires manual recovery."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "apt-unmet-dependency"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Unmet dependencies"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Package dependency conflict"
|
||||||
|
body = "apt can't resolve a dependency. Try: sudo apt --fix-broken install — this is more automatic than dnf's conflict resolution."
|
||||||
|
|
||||||
|
# ── AppArmor (replaces SELinux) ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "apparmor-denial"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "apparmor=\"DENIED\""
|
||||||
|
severity = "info"
|
||||||
|
title = "AppArmor access denied"
|
||||||
|
body = "Debian/Ubuntu ships AppArmor instead of SELinux. The concepts are similar but the tooling differs. Check: sudo aa-status — for audit logs: sudo aa-logprof — profiles are in /etc/apparmor.d/"
|
||||||
|
|
||||||
|
# ── System ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "kernel-driver-firmware"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "firmware: failed to load"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Firmware file missing"
|
||||||
|
body = "sudo apt install firmware-linux firmware-linux-nonfree — unlike Fedora where firmware comes via linux-firmware, Debian splits it into free/nonfree packages. Enable non-free in /etc/apt/sources.list first."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "oom-killer"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Out of memory: Kill process"
|
||||||
|
severity = "warn"
|
||||||
|
title = "OOM killer fired"
|
||||||
|
body = "A process was killed for RAM. Ubuntu enables zswap by default; Debian doesn't always. Add a swapfile or enable zswap via /sys/module/zswap/parameters/enabled"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "disk-io-error"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Buffer I/O error on device"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Disk I/O error"
|
||||||
|
body = "Check SMART: sudo smartctl -a /dev/sdX — install: sudo apt install smartmontools"
|
||||||
|
|
||||||
|
# ── Audio ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pipewire-connect-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Failed to connect to PipeWire"
|
||||||
|
severity = "warn"
|
||||||
|
title = "PipeWire not responding"
|
||||||
|
body = "Ubuntu 22.04+ and Debian 12+ ship PipeWire. Restart: systemctl --user restart pipewire pipewire-pulse wireplumber"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pulseaudio-connect-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Failed to connect to pulseaudio"
|
||||||
|
severity = "warn"
|
||||||
|
title = "PulseAudio not responding"
|
||||||
|
body = "Older Debian/Ubuntu systems use PulseAudio instead of PipeWire. Restart: pulseaudio --kill && pulseaudio --start"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "bluetooth-rfkill-blocked"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Blocked through rfkill"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Bluetooth rfkill blocked"
|
||||||
|
body = "rfkill unblock bluetooth — same as Fedora."
|
||||||
|
|
||||||
|
# ── GPU / display ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "gpu-hang"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "GPU HANG"
|
||||||
|
severity = "warn"
|
||||||
|
title = "GPU hang"
|
||||||
|
body = "GPU stopped responding. For NVIDIA on Debian/Ubuntu: sudo apt install nvidia-driver — or use ubuntu-drivers autoinstall on Ubuntu. Similar to Fedora's RPM Fusion approach."
|
||||||
|
|
||||||
|
# ── Network ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "networkmanager-activation-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Activation failed"
|
||||||
|
severity = "info"
|
||||||
|
title = "NetworkManager: connection failed"
|
||||||
|
body = "nmcli device status — Debian may use ifupdown instead of NetworkManager on minimal installs. Check: systemctl status NetworkManager"
|
||||||
|
|
||||||
|
# ── Media ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "missing-codec"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "GStreamer: Failed to find plugin"
|
||||||
|
severity = "info"
|
||||||
|
title = "Missing media codec"
|
||||||
|
body = "On Ubuntu/Mint: sudo apt install ubuntu-restricted-extras — on Debian: enable non-free and install libavcodec-extra. Fedora's RPM Fusion serves the same purpose."
|
||||||
|
|
||||||
|
# ── Gaming ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "proton-runtime-missing"
|
||||||
|
sources = ["applog:proton"]
|
||||||
|
match_text = "wine: cannot find"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Proton runtime issue"
|
||||||
|
body = "Right-click game in Steam -> Properties -> Local Files -> Verify integrity."
|
||||||
130
src-tauri/patterns/fedora-to-opensuse.toml
Normal file
130
src-tauri/patterns/fedora-to-opensuse.toml
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
[meta]
|
||||||
|
source_os = "linux"
|
||||||
|
target_distro_family = "opensuse"
|
||||||
|
|
||||||
|
# Fedora/RHEL user moving to openSUSE Tumbleweed or Leap.
|
||||||
|
# Body text assumes DNF and RPM familiarity; both use RPM so tooling overlaps.
|
||||||
|
|
||||||
|
[log_paths]
|
||||||
|
steam = "~/.local/share/Steam/logs/content_log.txt"
|
||||||
|
proton = "~/.local/share/Steam/logs/proton_log.txt"
|
||||||
|
|
||||||
|
# ── zypper / RPM ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "zypper-lock"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "System management is locked"
|
||||||
|
severity = "warn"
|
||||||
|
title = "zypper package manager is locked"
|
||||||
|
body = "Another zypper or PackageKit process is running — same situation as dnf being held by dnf-automatic. Wait it out or check: sudo ps aux | grep zypper"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "zypper-dep-conflict"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "conflicts with"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Package dependency conflict"
|
||||||
|
body = "zypper presents conflicts interactively. Both Fedora's dnf and zypper use RPM, but zypper's solver can be more conservative. Try: sudo zypper dup (distribution upgrade) for more aggressive resolution."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "zypper-gpg-key"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "does not verify"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Repository signature not trusted"
|
||||||
|
body = "Auto-import: sudo zypper --gpg-auto-import-keys ref — similar to dnf's GPG key prompts but the accept syntax differs."
|
||||||
|
|
||||||
|
# ── AppArmor (replaces SELinux) ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "apparmor-denial"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "apparmor=\"DENIED\""
|
||||||
|
severity = "info"
|
||||||
|
title = "AppArmor access denied"
|
||||||
|
body = "openSUSE ships AppArmor, not SELinux like Fedora. Similar purpose but different tooling. Check: sudo aa-status — audit: sudo aa-logprof — you'll need to rebuild your mental model from SELinux policy types to AppArmor profiles."
|
||||||
|
|
||||||
|
# ── System ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "kernel-driver-firmware"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "firmware: failed to load"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Firmware file missing"
|
||||||
|
body = "sudo zypper install kernel-firmware — openSUSE uses a single kernel-firmware package similar to Fedora's linux-firmware."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "oom-killer"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Out of memory: Kill process"
|
||||||
|
severity = "warn"
|
||||||
|
title = "OOM killer fired"
|
||||||
|
body = "A process was killed for RAM. openSUSE prompts for swap setup during install. If skipped: sudo dd if=/dev/zero of=/swapfile bs=1M count=4096 && sudo mkswap /swapfile && sudo swapon /swapfile"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "disk-io-error"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Buffer I/O error on device"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Disk I/O error"
|
||||||
|
body = "Check SMART: sudo smartctl -a /dev/sdX — install: sudo zypper install smartmontools"
|
||||||
|
|
||||||
|
# ── YaST (openSUSE-specific) ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "yast-backend-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "YaST got signal"
|
||||||
|
severity = "warn"
|
||||||
|
title = "YaST configuration tool crashed"
|
||||||
|
body = "YaST is openSUSE's graphical admin tool (no Fedora equivalent). If it crashed mid-operation, check what it was doing: sudo yast2 -- the text mode version often recovers where the GUI fails."
|
||||||
|
|
||||||
|
# ── Audio ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pipewire-connect-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Failed to connect to PipeWire"
|
||||||
|
severity = "warn"
|
||||||
|
title = "PipeWire not responding"
|
||||||
|
body = "Tumbleweed ships PipeWire like Fedora. Restart: systemctl --user restart pipewire pipewire-pulse wireplumber"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "bluetooth-rfkill-blocked"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Blocked through rfkill"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Bluetooth rfkill blocked"
|
||||||
|
body = "rfkill unblock bluetooth — same as Fedora."
|
||||||
|
|
||||||
|
# ── GPU / display ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "gpu-hang"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "GPU HANG"
|
||||||
|
severity = "warn"
|
||||||
|
title = "GPU hang"
|
||||||
|
body = "GPU stopped responding. For NVIDIA on openSUSE: use the NVIDIA OBS repo or packages.opensuse.org — similar to RPM Fusion on Fedora."
|
||||||
|
|
||||||
|
# ── Network ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "networkmanager-activation-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Activation failed"
|
||||||
|
severity = "info"
|
||||||
|
title = "NetworkManager: connection failed"
|
||||||
|
body = "nmcli device status — openSUSE uses NetworkManager or Wicked depending on the install profile. Check which is active: systemctl status NetworkManager wicked"
|
||||||
|
|
||||||
|
# ── Gaming ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "proton-runtime-missing"
|
||||||
|
sources = ["applog:proton"]
|
||||||
|
match_text = "wine: cannot find"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Proton runtime issue"
|
||||||
|
body = "Right-click game in Steam -> Properties -> Local Files -> Verify integrity. For Steam on openSUSE: sudo zypper install steam (from the games repo on OBS)."
|
||||||
172
src-tauri/patterns/opensuse-to-arch.toml
Normal file
172
src-tauri/patterns/opensuse-to-arch.toml
Normal file
|
|
@ -0,0 +1,172 @@
|
||||||
|
[meta]
|
||||||
|
source_os = "linux"
|
||||||
|
target_distro_family = "arch"
|
||||||
|
|
||||||
|
# openSUSE Tumbleweed/Leap user moving to Arch.
|
||||||
|
# Body text assumes zypper, YaST, and AppArmor familiarity.
|
||||||
|
|
||||||
|
[log_paths]
|
||||||
|
pacman = "/var/log/pacman.log"
|
||||||
|
steam = "~/.local/share/Steam/logs/content_log.txt"
|
||||||
|
proton = "~/.local/share/Steam/logs/proton_log.txt"
|
||||||
|
lutris = "~/.cache/lutris/logs/lutris.log"
|
||||||
|
|
||||||
|
# ── pacman / AUR ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pacman-db-lock"
|
||||||
|
sources = ["journald", "applog:pacman"]
|
||||||
|
match_text = "could not lock database: File exists"
|
||||||
|
severity = "warn"
|
||||||
|
title = "pacman database locked"
|
||||||
|
body = "Lock file left from a crashed pacman run — like zypper's /var/run/zypp.pid getting orphaned. Remove if nothing is running: sudo rm /var/lib/pacman/db.lck"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "partial-upgrade-warning"
|
||||||
|
sources = ["applog:pacman"]
|
||||||
|
match_text = "warning: database file for"
|
||||||
|
severity = "info"
|
||||||
|
title = "Package database out of sync"
|
||||||
|
body = "On Arch, syncing without upgrading is dangerous — unlike openSUSE where zypper ref is safe to run alone. Always: pacman -Syu — never pacman -Sy without the u."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pacman-dep-conflict"
|
||||||
|
sources = ["journald", "applog:pacman"]
|
||||||
|
match_text = "conflicting dependencies"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Dependency conflict"
|
||||||
|
body = "Unlike zypper which resolves conflicts interactively, pacman puts the choice directly on you. Read the conflict — usually one package replaces another. Remove the conflicting package first."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "aur-build-failure"
|
||||||
|
sources = ["journald", "applog:pacman"]
|
||||||
|
match_text = "error: failed to build"
|
||||||
|
severity = "warn"
|
||||||
|
title = "AUR package build failed"
|
||||||
|
body = "The AUR has no binary packages — makepkg compiles from source every time. There's no OBS equivalent here. Check the build log and the AUR comments page for the package."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "aur-pgp-key"
|
||||||
|
sources = ["journald", "applog:pacman"]
|
||||||
|
match_text = "unknown public key"
|
||||||
|
severity = "warn"
|
||||||
|
title = "PGP key not in keyring"
|
||||||
|
body = "Import the key: gpg --recv-keys <keyid> — different from zypper's --gpg-auto-import-keys; this is GnuPG's personal keyring used by makepkg."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "chaotic-aur-sig-fail"
|
||||||
|
sources = ["journald", "applog:pacman"]
|
||||||
|
match_text = "signature from"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Package signature verification failed"
|
||||||
|
body = "Chaotic-AUR key not trusted. Import: sudo pacman-key --recv-key 3056513887B78AEB --keyserver keyserver.ubuntu.com && sudo pacman-key --lsign-key 3056513887B78AEB"
|
||||||
|
|
||||||
|
# ── AppArmor → none ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "apparmor-remnant"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "apparmor=\"DENIED\""
|
||||||
|
severity = "info"
|
||||||
|
title = "AppArmor log entry (no AppArmor on Arch by default)"
|
||||||
|
body = "Arch doesn't ship AppArmor by default. If you see this, you may have installed apparmor from the AUR manually. Run: sudo systemctl disable apparmor if you don't need it."
|
||||||
|
|
||||||
|
# ── Kernel / DKMS ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "dkms-build-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Error! Build of"
|
||||||
|
severity = "warn"
|
||||||
|
title = "DKMS module failed to build"
|
||||||
|
body = "A kernel module didn't compile. Arch's rolling kernel updates more frequently than Tumbleweed's. Check: dkms status — reinstall the failing dkms package."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "kernel-driver-firmware"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "firmware: failed to load"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Firmware file missing"
|
||||||
|
body = "sudo pacman -S linux-firmware — equivalent to openSUSE's kernel-firmware."
|
||||||
|
|
||||||
|
# ── System ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "locale-not-set"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Cannot set LC_ALL to default locale"
|
||||||
|
severity = "info"
|
||||||
|
title = "Locale not generated"
|
||||||
|
body = "Unlike YaST which configured this for you, Arch requires manual locale setup. Edit /etc/locale.gen, uncomment your locale, run: sudo locale-gen — set LANG in /etc/locale.conf."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "oom-killer"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Out of memory: Kill process"
|
||||||
|
severity = "warn"
|
||||||
|
title = "OOM killer fired"
|
||||||
|
body = "Arch doesn't set up swap by default like openSUSE's installer does. Add zram: sudo pacman -S zram-generator — or add a swapfile."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "disk-io-error"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Buffer I/O error on device"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Disk I/O error"
|
||||||
|
body = "Check SMART: sudo smartctl -a /dev/sdX — install: sudo pacman -S smartmontools"
|
||||||
|
|
||||||
|
# ── Audio ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pipewire-connect-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Failed to connect to PipeWire"
|
||||||
|
severity = "warn"
|
||||||
|
title = "PipeWire not responding"
|
||||||
|
body = "Both openSUSE and Arch ship PipeWire. Restart: systemctl --user restart pipewire pipewire-pulse wireplumber"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "bluetooth-rfkill-blocked"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Blocked through rfkill"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Bluetooth rfkill blocked"
|
||||||
|
body = "rfkill unblock bluetooth"
|
||||||
|
|
||||||
|
# ── GPU / display ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "gpu-hang"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "GPU HANG"
|
||||||
|
severity = "warn"
|
||||||
|
title = "GPU hang"
|
||||||
|
body = "GPU stopped responding. On Arch: AMD uses mesa (official repos), NVIDIA uses nvidia or nvidia-dkms. No OBS equivalent — everything is in pacman or the AUR."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "xwayland-crash"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "XWayland server terminated unexpectedly"
|
||||||
|
severity = "warn"
|
||||||
|
title = "XWayland crashed"
|
||||||
|
body = "X11 apps dead until session restart. Same behavior as on openSUSE Wayland sessions."
|
||||||
|
|
||||||
|
# ── Network ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "networkmanager-activation-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Activation failed"
|
||||||
|
severity = "info"
|
||||||
|
title = "NetworkManager: connection failed"
|
||||||
|
body = "nmcli device status — Arch uses NetworkManager (not Wicked). If a wifi adapter is missing, check dmesg for firmware errors."
|
||||||
|
|
||||||
|
# ── Gaming ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "proton-runtime-missing"
|
||||||
|
sources = ["applog:proton"]
|
||||||
|
match_text = "wine: cannot find"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Proton runtime issue"
|
||||||
|
body = "Right-click game in Steam -> Properties -> Local Files -> Verify integrity."
|
||||||
138
src-tauri/patterns/opensuse-to-debian.toml
Normal file
138
src-tauri/patterns/opensuse-to-debian.toml
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
[meta]
|
||||||
|
source_os = "linux"
|
||||||
|
target_distro_family = "debian"
|
||||||
|
|
||||||
|
# openSUSE Tumbleweed/Leap user moving to Debian/Ubuntu/Mint.
|
||||||
|
# Body text assumes zypper, YaST, and AppArmor familiarity.
|
||||||
|
|
||||||
|
[log_paths]
|
||||||
|
steam = "~/.local/share/Steam/logs/content_log.txt"
|
||||||
|
proton = "~/.local/share/Steam/logs/proton_log.txt"
|
||||||
|
|
||||||
|
# ── apt / dpkg ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "apt-lock"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Could not get lock /var/lib/dpkg/lock"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Package manager is locked"
|
||||||
|
body = "Another apt process is running — often unattended-upgrades (no zypper equivalent, but similar to PackageKit background updates). Wait a minute. If stuck: sudo rm /var/lib/dpkg/lock-frontend /var/lib/dpkg/lock && sudo dpkg --configure -a"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "dpkg-interrupted"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "dpkg was interrupted"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Package install was interrupted"
|
||||||
|
body = "Like a zypper transaction that got killed, but dpkg needs manual recovery. Fix: sudo dpkg --configure -a"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "apt-unmet-dependency"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Unmet dependencies"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Package dependency conflict"
|
||||||
|
body = "apt auto-resolves most conflicts — less interactive than zypper's conflict wizard. Let it try: sudo apt --fix-broken install"
|
||||||
|
|
||||||
|
# ── AppArmor ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "apparmor-denial"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "apparmor=\"DENIED\""
|
||||||
|
severity = "info"
|
||||||
|
title = "AppArmor access denied"
|
||||||
|
body = "Both openSUSE and Debian/Ubuntu use AppArmor — the tooling is the same. Check: sudo aa-status — audit: sudo aa-logprof — profiles: /etc/apparmor.d/"
|
||||||
|
|
||||||
|
# ── System ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "kernel-driver-firmware"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "firmware: failed to load"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Firmware file missing"
|
||||||
|
body = "On Debian: sudo apt install firmware-linux firmware-linux-nonfree (enable non-free sources first). On Ubuntu: sudo apt install linux-firmware. Debian splits firmware by license unlike openSUSE's single kernel-firmware package."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "oom-killer"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Out of memory: Kill process"
|
||||||
|
severity = "warn"
|
||||||
|
title = "OOM killer fired"
|
||||||
|
body = "A process was killed for RAM. openSUSE's installer sets up swap; Debian minimal may not. Add a swapfile: sudo fallocate -l 4G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "disk-io-error"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Buffer I/O error on device"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Disk I/O error"
|
||||||
|
body = "Check SMART: sudo smartctl -a /dev/sdX — install: sudo apt install smartmontools"
|
||||||
|
|
||||||
|
# ── Audio ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pipewire-connect-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Failed to connect to PipeWire"
|
||||||
|
severity = "warn"
|
||||||
|
title = "PipeWire not responding"
|
||||||
|
body = "Both Tumbleweed and Ubuntu 22.04+/Debian 12+ ship PipeWire. Restart: systemctl --user restart pipewire pipewire-pulse wireplumber"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pulseaudio-connect-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Failed to connect to pulseaudio"
|
||||||
|
severity = "warn"
|
||||||
|
title = "PulseAudio not responding"
|
||||||
|
body = "Older Debian systems still use PulseAudio. Restart: pulseaudio --kill && pulseaudio --start"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "bluetooth-rfkill-blocked"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Blocked through rfkill"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Bluetooth rfkill blocked"
|
||||||
|
body = "rfkill unblock bluetooth — same as openSUSE."
|
||||||
|
|
||||||
|
# ── GPU / display ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "gpu-hang"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "GPU HANG"
|
||||||
|
severity = "warn"
|
||||||
|
title = "GPU hang"
|
||||||
|
body = "GPU stopped responding. For NVIDIA on Ubuntu: ubuntu-drivers autoinstall — on Debian: apt install nvidia-driver (requires non-free). Unlike openSUSE's OBS NVIDIA repo, Ubuntu keeps drivers in the main archive."
|
||||||
|
|
||||||
|
# ── Network ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "networkmanager-activation-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Activation failed"
|
||||||
|
severity = "info"
|
||||||
|
title = "NetworkManager: connection failed"
|
||||||
|
body = "nmcli device status — Debian minimal may use ifupdown instead of NetworkManager. Install if missing: sudo apt install network-manager"
|
||||||
|
|
||||||
|
# ── Printing ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "cups-server-error"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Unable to connect to CUPS server"
|
||||||
|
severity = "info"
|
||||||
|
title = "Printer service not running"
|
||||||
|
body = "sudo systemctl start cups && sudo systemctl enable cups — YaST auto-configured printing on openSUSE; Debian leaves CUPS disabled until you enable it."
|
||||||
|
|
||||||
|
# ── Gaming ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "proton-runtime-missing"
|
||||||
|
sources = ["applog:proton"]
|
||||||
|
match_text = "wine: cannot find"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Proton runtime issue"
|
||||||
|
body = "Right-click game in Steam -> Properties -> Local Files -> Verify integrity."
|
||||||
146
src-tauri/patterns/opensuse-to-fedora.toml
Normal file
146
src-tauri/patterns/opensuse-to-fedora.toml
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
[meta]
|
||||||
|
source_os = "linux"
|
||||||
|
target_distro_family = "fedora"
|
||||||
|
|
||||||
|
# openSUSE Tumbleweed/Leap user moving to Fedora.
|
||||||
|
# Body text assumes zypper, YaST, and AppArmor familiarity; both use RPM.
|
||||||
|
|
||||||
|
[log_paths]
|
||||||
|
steam = "~/.local/share/Steam/logs/content_log.txt"
|
||||||
|
proton = "~/.local/share/Steam/logs/proton_log.txt"
|
||||||
|
|
||||||
|
# ── DNF / RPM ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "dnf-lock"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Another app is currently holding the dnf lock"
|
||||||
|
severity = "warn"
|
||||||
|
title = "DNF package manager is locked"
|
||||||
|
body = "dnf-automatic (Fedora's background updater) is probably running — similar to PackageKit on openSUSE. Wait it out or check: sudo ps aux | grep dnf"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "dnf-dep-conflict"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "conflicts with"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Package dependency conflict"
|
||||||
|
body = "Both use RPM but their solvers differ. DNF auto-resolves more aggressively than zypper. If dnf can't fix it: sudo dnf distro-sync — the equivalent of zypper dup."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "dnf-gpg-key"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "GPG key retrieval failed"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Repository GPG key missing"
|
||||||
|
body = "Import: sudo rpm --import /path/to/key.gpg — same rpm command as openSUSE. RPM Fusion keys are imported automatically when you enable the repo."
|
||||||
|
|
||||||
|
# ── SELinux (replaces AppArmor) ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "selinux-denial"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "type=AVC"
|
||||||
|
severity = "info"
|
||||||
|
title = "SELinux access denied"
|
||||||
|
body = "Fedora uses SELinux instead of openSUSE's AppArmor. Both are MAC systems but with different models — SELinux uses type enforcement, AppArmor uses path-based profiles. Check: ausearch -m AVC -ts recent — get a fix: sealert -a /var/log/audit/audit.log"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "selinux-context-wrong"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "restorecon"
|
||||||
|
severity = "info"
|
||||||
|
title = "SELinux file context mismatch"
|
||||||
|
body = "Files copied from openSUSE or an external drive may have wrong SELinux labels. Fix: sudo restorecon -Rv /path/to/file — equivalent to aa-relabel in AppArmor terms."
|
||||||
|
|
||||||
|
# ── System ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "kernel-driver-firmware"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "firmware: failed to load"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Firmware file missing"
|
||||||
|
body = "sudo dnf install linux-firmware — same scope as openSUSE's kernel-firmware. Some chips need RPM Fusion nonfree: sudo dnf install rpmfusion-nonfree-release-$(rpm -E %fedora)"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "oom-killer"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Out of memory: Kill process"
|
||||||
|
severity = "warn"
|
||||||
|
title = "OOM killer fired"
|
||||||
|
body = "A process was killed for RAM. Fedora enables zswap by default on modern releases. For zram: sudo dnf install zram-generator — similar setup to openSUSE."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "disk-io-error"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Buffer I/O error on device"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Disk I/O error"
|
||||||
|
body = "Check SMART: sudo smartctl -a /dev/sdX — install: sudo dnf install smartmontools"
|
||||||
|
|
||||||
|
# ── YaST → no YaST ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "yast-not-found"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "yast: command not found"
|
||||||
|
severity = "info"
|
||||||
|
title = "YaST not available on Fedora"
|
||||||
|
body = "Fedora has no YaST equivalent — use GNOME Settings for display/network/user config, and dnf/rpm for package management. Most things YaST handled are done via systemctl, nmcli, or the GNOME control center."
|
||||||
|
|
||||||
|
# ── Audio ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pipewire-connect-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Failed to connect to PipeWire"
|
||||||
|
severity = "warn"
|
||||||
|
title = "PipeWire not responding"
|
||||||
|
body = "Both Tumbleweed and Fedora ship PipeWire. Restart: systemctl --user restart pipewire pipewire-pulse wireplumber"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "bluetooth-rfkill-blocked"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Blocked through rfkill"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Bluetooth rfkill blocked"
|
||||||
|
body = "rfkill unblock bluetooth — same as openSUSE."
|
||||||
|
|
||||||
|
# ── GPU / display ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "gpu-hang"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "GPU HANG"
|
||||||
|
severity = "warn"
|
||||||
|
title = "GPU hang"
|
||||||
|
body = "GPU stopped responding. For NVIDIA on Fedora: sudo dnf install akmod-nvidia (from RPM Fusion) — similar to openSUSE's NVIDIA OBS repo but uses akmods instead of DKMS."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "xwayland-crash"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "XWayland server terminated unexpectedly"
|
||||||
|
severity = "warn"
|
||||||
|
title = "XWayland crashed"
|
||||||
|
body = "Fedora GNOME defaults to Wayland like openSUSE's GNOME spin. X11 apps dead until session restart."
|
||||||
|
|
||||||
|
# ── Network ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "networkmanager-activation-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Activation failed"
|
||||||
|
severity = "info"
|
||||||
|
title = "NetworkManager: connection failed"
|
||||||
|
body = "nmcli device status — Fedora uses NetworkManager, not Wicked. If you had Wicked-specific configs on openSUSE, recreate them in NetworkManager format."
|
||||||
|
|
||||||
|
# ── Gaming ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "proton-runtime-missing"
|
||||||
|
sources = ["applog:proton"]
|
||||||
|
match_text = "wine: cannot find"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Proton runtime issue"
|
||||||
|
body = "Right-click game in Steam -> Properties -> Local Files -> Verify integrity. Steam on Fedora: sudo dnf install steam (from RPM Fusion free)."
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::config::{MigrationConfig, NotificationLevel, RobinConfig, SourceOs};
|
use crate::config::{MigrationConfig, NotificationLevel, RobinConfig, SourceOs};
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use tauri::State;
|
use tauri::{Emitter, State};
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub config: Mutex<RobinConfig>,
|
pub config: Mutex<RobinConfig>,
|
||||||
|
|
@ -28,6 +28,7 @@ pub fn needs_onboarding(state: State<'_, AppState>) -> bool {
|
||||||
pub fn complete_onboarding(
|
pub fn complete_onboarding(
|
||||||
source_os: String,
|
source_os: String,
|
||||||
distro: String,
|
distro: String,
|
||||||
|
source_distro: Option<String>,
|
||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let source = match source_os.to_lowercase().as_str() {
|
let source = match source_os.to_lowercase().as_str() {
|
||||||
|
|
@ -43,10 +44,18 @@ pub fn complete_onboarding(
|
||||||
distro
|
distro
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// For Linux-to-Linux migrations, derive source family from the reported source distro.
|
||||||
|
// If a source_distro was passed explicitly, use distro_family() on it; otherwise leave None.
|
||||||
|
let source_distro_family = source_distro.as_deref().map(|sd| {
|
||||||
|
let family = crate::distro::distro_family(sd);
|
||||||
|
if family == "unknown" { None } else { Some(family.to_string()) }
|
||||||
|
}).flatten();
|
||||||
|
|
||||||
let mut config = state.config.lock().map_err(|e| e.to_string())?;
|
let mut config = state.config.lock().map_err(|e| e.to_string())?;
|
||||||
config.migration = Some(MigrationConfig {
|
config.migration = Some(MigrationConfig {
|
||||||
source_os: source,
|
source_os: source,
|
||||||
distro: detected,
|
distro: detected,
|
||||||
|
source_distro_family,
|
||||||
fluency_level: 0,
|
fluency_level: 0,
|
||||||
});
|
});
|
||||||
config.save().map_err(|e| e.to_string())
|
config.save().map_err(|e| e.to_string())
|
||||||
|
|
@ -79,3 +88,48 @@ pub fn panel_opened() {
|
||||||
pub fn panel_closed() {
|
pub fn panel_closed() {
|
||||||
crate::notify::set_panel_open(false);
|
crate::notify::set_panel_open(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn chat(
|
||||||
|
message: String,
|
||||||
|
state: State<'_, AppState>,
|
||||||
|
app_handle: tauri::AppHandle,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let (base_url, model, source_os, distro) = {
|
||||||
|
let cfg = state.config.lock().map_err(|e| e.to_string())?;
|
||||||
|
let (source_os, distro) = if let Some(ref m) = cfg.migration {
|
||||||
|
let os = match m.source_os {
|
||||||
|
crate::config::SourceOs::Macos => "macOS",
|
||||||
|
crate::config::SourceOs::Windows => "Windows",
|
||||||
|
crate::config::SourceOs::Linux => "Linux",
|
||||||
|
crate::config::SourceOs::Unknown => "Unknown",
|
||||||
|
};
|
||||||
|
(os.to_string(), m.distro.clone())
|
||||||
|
} else {
|
||||||
|
("Unknown".to_string(), "unknown".to_string())
|
||||||
|
};
|
||||||
|
(
|
||||||
|
cfg.ollama.base_url.clone(),
|
||||||
|
cfg.ollama.model.clone(),
|
||||||
|
source_os,
|
||||||
|
distro,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let system_prompt = crate::llm::build_system_prompt(&source_os, &distro);
|
||||||
|
let messages = vec![
|
||||||
|
crate::llm::ChatMessage { role: "system".into(), content: system_prompt },
|
||||||
|
crate::llm::ChatMessage { role: "user".into(), content: message },
|
||||||
|
];
|
||||||
|
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
if let Err(e) = crate::llm::chat_stream(&base_url, &model, messages, &app_handle).await {
|
||||||
|
log::error!("chat stream error: {e}");
|
||||||
|
if let Err(emit_err) = app_handle.emit("robin:chat-error", e.to_string()) {
|
||||||
|
log::warn!("failed to emit robin:chat-error: {emit_err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,10 @@ pub struct MigrationConfig {
|
||||||
pub source_os: SourceOs,
|
pub source_os: SourceOs,
|
||||||
/// Detected distro string, e.g. "cachyos", "linuxmint"
|
/// Detected distro string, e.g. "cachyos", "linuxmint"
|
||||||
pub distro: String,
|
pub distro: String,
|
||||||
|
/// Source distro family for Linux-to-Linux migrations, e.g. "debian", "fedora", "arch", "opensuse"
|
||||||
|
/// Used to load a more specific pattern file (e.g. debian-to-arch) before the generic linux-to-arch fallback.
|
||||||
|
#[serde(default)]
|
||||||
|
pub source_distro_family: Option<String>,
|
||||||
/// 0–5: grows as user dismisses suggestions they already know
|
/// 0–5: grows as user dismisses suggestions they already know
|
||||||
pub fluency_level: u8,
|
pub fluency_level: u8,
|
||||||
}
|
}
|
||||||
|
|
@ -138,6 +142,7 @@ mod tests {
|
||||||
config.migration = Some(MigrationConfig {
|
config.migration = Some(MigrationConfig {
|
||||||
source_os: SourceOs::Macos,
|
source_os: SourceOs::Macos,
|
||||||
distro: "cachyos".into(),
|
distro: "cachyos".into(),
|
||||||
|
source_distro_family: None,
|
||||||
fluency_level: 0,
|
fluency_level: 0,
|
||||||
});
|
});
|
||||||
assert!(!config.needs_onboarding());
|
assert!(!config.needs_onboarding());
|
||||||
|
|
@ -149,6 +154,7 @@ mod tests {
|
||||||
migration: Some(MigrationConfig {
|
migration: Some(MigrationConfig {
|
||||||
source_os: SourceOs::Windows,
|
source_os: SourceOs::Windows,
|
||||||
distro: "linuxmint".into(),
|
distro: "linuxmint".into(),
|
||||||
|
source_distro_family: None,
|
||||||
fluency_level: 2,
|
fluency_level: 2,
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
mod commands;
|
mod commands;
|
||||||
mod config;
|
mod config;
|
||||||
mod distro;
|
mod distro;
|
||||||
|
mod llm;
|
||||||
mod notify;
|
mod notify;
|
||||||
mod patterns;
|
mod patterns;
|
||||||
mod tray;
|
mod tray;
|
||||||
|
|
@ -41,7 +42,7 @@ pub fn run() {
|
||||||
config::SourceOs::Linux => "linux",
|
config::SourceOs::Linux => "linux",
|
||||||
config::SourceOs::Unknown => "unknown",
|
config::SourceOs::Unknown => "unknown",
|
||||||
};
|
};
|
||||||
patterns::load(source, family).ok()
|
patterns::load(source, migration.source_distro_family.as_deref(), family).ok()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
@ -76,6 +77,7 @@ pub fn run() {
|
||||||
commands::get_pending_events,
|
commands::get_pending_events,
|
||||||
commands::panel_opened,
|
commands::panel_opened,
|
||||||
commands::panel_closed,
|
commands::panel_closed,
|
||||||
|
commands::chat,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running Robin");
|
.expect("error while running Robin");
|
||||||
|
|
|
||||||
157
src-tauri/src/llm.rs
Normal file
157
src-tauri/src/llm.rs
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tauri::Emitter;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct ChatMessage {
|
||||||
|
pub role: String,
|
||||||
|
pub content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct OllamaChunk {
|
||||||
|
message: Option<OllamaChunkMessage>,
|
||||||
|
#[serde(default)]
|
||||||
|
done: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct OllamaChunkMessage {
|
||||||
|
content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a migration-aware system prompt from the user's source OS and current distro.
|
||||||
|
pub fn build_system_prompt(source_os: &str, distro: &str) -> String {
|
||||||
|
format!(
|
||||||
|
"You are Robin, a friendly Linux migration assistant running locally on the user's machine. \
|
||||||
|
The user is migrating from {source_os} to {distro}. \
|
||||||
|
Help them with Linux questions, explain differences from their previous OS, \
|
||||||
|
and provide practical command-line solutions. \
|
||||||
|
Be concise, accurate, and use examples when helpful. \
|
||||||
|
Never suggest Windows or macOS solutions — focus on Linux."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stream a chat response from Ollama, emitting tokens as Tauri events.
|
||||||
|
///
|
||||||
|
/// Emits: `robin:chat-token` (String), `robin:chat-done` (unit), `robin:chat-error` (String).
|
||||||
|
pub async fn chat_stream(
|
||||||
|
base_url: &str,
|
||||||
|
model: &str,
|
||||||
|
messages: Vec<ChatMessage>,
|
||||||
|
app: &tauri::AppHandle,
|
||||||
|
) -> Result<()> {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let url = format!("{base_url}/api/chat");
|
||||||
|
|
||||||
|
let body = serde_json::json!({
|
||||||
|
"model": model,
|
||||||
|
"messages": messages,
|
||||||
|
"stream": true,
|
||||||
|
});
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.post(&url)
|
||||||
|
.json(&body)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("failed to connect to Ollama at {url}"))?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
let status = response.status();
|
||||||
|
let text = response.text().await.unwrap_or_default();
|
||||||
|
anyhow::bail!("Ollama returned {status}: {text}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer bytes across chunks to handle UTF-8 sequences split at chunk boundaries.
|
||||||
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
|
let mut response = response;
|
||||||
|
|
||||||
|
while let Some(chunk) = response.chunk().await.context("stream read error")? {
|
||||||
|
buf.extend_from_slice(&chunk);
|
||||||
|
|
||||||
|
// Extract complete newline-delimited JSON lines from the buffer.
|
||||||
|
while let Some(pos) = buf.iter().position(|&b| b == b'\n') {
|
||||||
|
let line_bytes: Vec<u8> = buf.drain(..=pos).collect();
|
||||||
|
let line = String::from_utf8_lossy(&line_bytes);
|
||||||
|
let line = line.trim();
|
||||||
|
if line.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match serde_json::from_str::<OllamaChunk>(line) {
|
||||||
|
Ok(chunk) => {
|
||||||
|
if let Some(msg) = chunk.message {
|
||||||
|
if !msg.content.is_empty() {
|
||||||
|
if let Err(e) = app.emit("robin:chat-token", msg.content) {
|
||||||
|
log::warn!("failed to emit robin:chat-token: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if chunk.done {
|
||||||
|
if let Err(e) = app.emit("robin:chat-done", ()) {
|
||||||
|
log::warn!("failed to emit robin:chat-done: {e}");
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("failed to parse Ollama chunk '{}': {e}", line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream ended without a done=true chunk — emit done anyway so frontend unblocks.
|
||||||
|
if let Err(e) = app.emit("robin:chat-done", ()) {
|
||||||
|
log::warn!("failed to emit robin:chat-done at stream end: {e}");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn system_prompt_contains_source_and_distro() {
|
||||||
|
let prompt = build_system_prompt("Windows", "cachyos");
|
||||||
|
assert!(prompt.contains("Windows"));
|
||||||
|
assert!(prompt.contains("cachyos"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn system_prompt_contains_robin() {
|
||||||
|
let prompt = build_system_prompt("macOS", "arch");
|
||||||
|
assert!(prompt.to_lowercase().contains("robin"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ollama_chunk_parses_token() {
|
||||||
|
let json = r#"{"model":"llama3.2","message":{"role":"assistant","content":"Hello"},"done":false}"#;
|
||||||
|
let chunk: OllamaChunk = serde_json::from_str(json).unwrap();
|
||||||
|
assert_eq!(chunk.message.unwrap().content, "Hello");
|
||||||
|
assert!(!chunk.done);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ollama_chunk_parses_done() {
|
||||||
|
let json = r#"{"model":"llama3.2","message":{"role":"assistant","content":""},"done":true}"#;
|
||||||
|
let chunk: OllamaChunk = serde_json::from_str(json).unwrap();
|
||||||
|
assert!(chunk.done);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ollama_chunk_parses_no_message() {
|
||||||
|
let json = r#"{"model":"llama3.2","done":true}"#;
|
||||||
|
let chunk: OllamaChunk = serde_json::from_str(json).unwrap();
|
||||||
|
assert!(chunk.message.is_none());
|
||||||
|
assert!(chunk.done);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn malformed_json_fails_to_parse() {
|
||||||
|
let json = r#"{"not_valid": }"#;
|
||||||
|
let result = serde_json::from_str::<OllamaChunk>(json);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -39,16 +39,25 @@ pub struct MatchedEvent {
|
||||||
|
|
||||||
/// Load the pattern file for a source OS and distro family.
|
/// Load the pattern file for a source OS and distro family.
|
||||||
///
|
///
|
||||||
/// Tries candidates in order: dev-relative path, src-tauri-relative path,
|
/// For Linux-to-Linux migrations, `source_distro_family` (e.g. "debian", "fedora") is
|
||||||
/// system path. The Tauri resource directory is the authoritative location
|
/// tried first: `debian-to-arch.toml` before the generic `linux-to-arch.toml` fallback.
|
||||||
/// at runtime; passing a base path is handled in Task 10's caller via candidate list.
|
/// Tries each candidate at three path depths: dev-relative, src-tauri-relative, system.
|
||||||
pub fn load(source_os: &str, distro_family: &str) -> Result<PatternFile> {
|
pub fn load(
|
||||||
let filename = format!("{source_os}-to-{distro_family}.toml");
|
source_os: &str,
|
||||||
let candidates = [
|
source_distro_family: Option<&str>,
|
||||||
format!("patterns/{filename}"),
|
distro_family: &str,
|
||||||
format!("src-tauri/patterns/{filename}"),
|
) -> Result<PatternFile> {
|
||||||
format!("/usr/share/robin/patterns/{filename}"),
|
let mut candidates: Vec<String> = Vec::new();
|
||||||
];
|
if let Some(src_distro) = source_distro_family {
|
||||||
|
let specific = format!("{src_distro}-to-{distro_family}.toml");
|
||||||
|
candidates.push(format!("patterns/{specific}"));
|
||||||
|
candidates.push(format!("src-tauri/patterns/{specific}"));
|
||||||
|
candidates.push(format!("/usr/share/robin/patterns/{specific}"));
|
||||||
|
}
|
||||||
|
let generic = format!("{source_os}-to-{distro_family}.toml");
|
||||||
|
candidates.push(format!("patterns/{generic}"));
|
||||||
|
candidates.push(format!("src-tauri/patterns/{generic}"));
|
||||||
|
candidates.push(format!("/usr/share/robin/patterns/{generic}"));
|
||||||
for path in &candidates {
|
for path in &candidates {
|
||||||
let content = match std::fs::read_to_string(path) {
|
let content = match std::fs::read_to_string(path) {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
|
|
@ -75,7 +84,7 @@ pub fn load(source_os: &str, distro_family: &str) -> Result<PatternFile> {
|
||||||
}
|
}
|
||||||
return Ok(pf);
|
return Ok(pf);
|
||||||
}
|
}
|
||||||
anyhow::bail!("pattern file not found: {filename}")
|
anyhow::bail!("pattern file not found for {source_os}-to-{distro_family}.toml")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue