feat: full pattern matrix — M1 complete, M2 LLM chat, 30+ pattern files #10

Open
pyr0ball wants to merge 27 commits from feat/patterns-expansion into main
17 changed files with 1999 additions and 13 deletions
Showing only changes of commit 19286e9860 - Show all commits

View 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."

View 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)."

View 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)."

View 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"

View 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)."

View 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)."

View 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."

View 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."

View 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)."

View 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."

View 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."

View 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)."

View file

@ -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(())
}

View file

@ -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>,
/// 05: grows as user dismisses suggestions they already know /// 05: 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()

View file

@ -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
View 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());
}
}

View file

@ -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]