feat: full pattern matrix — M1 complete, M2 LLM chat, 30+ pattern files #10
15 changed files with 1272 additions and 30 deletions
136
src-tauri/patterns/android-to-arch.toml
Normal file
136
src-tauri/patterns/android-to-arch.toml
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
[meta]
|
||||||
|
source_os = "android"
|
||||||
|
target_distro_family = "arch"
|
||||||
|
|
||||||
|
# Android user on their first Arch/CachyOS install.
|
||||||
|
# Assumes NO terminal experience (unless they used Termux).
|
||||||
|
# Every explanation starts from first principles.
|
||||||
|
# App Store analogy: pacman/AUR = Google Play + sideloading.
|
||||||
|
|
||||||
|
[log_paths]
|
||||||
|
steam = "~/.local/share/Steam/logs/content_log.txt"
|
||||||
|
proton = "~/.local/share/Steam/logs/proton_log.txt"
|
||||||
|
|
||||||
|
# ── Package management ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pacman-db-lock"
|
||||||
|
sources = ["journald", "applog:pacman"]
|
||||||
|
match_text = "could not lock database: File exists"
|
||||||
|
severity = "warn"
|
||||||
|
title = "App installer is busy"
|
||||||
|
body = "The package manager (Linux's equivalent of the Play Store) got interrupted and left a lock file. A lock file is a signal to other processes that says 'I'm running, don't start.' If nothing is installing right now, remove it: open a terminal, type exactly: sudo rm /var/lib/pacman/db.lck — then press Enter and try again. 'sudo' means 'run this as administrator.'"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "partial-upgrade-warning"
|
||||||
|
sources = ["applog:pacman"]
|
||||||
|
match_text = "warning: database file for"
|
||||||
|
severity = "info"
|
||||||
|
title = "App list is out of date — update everything"
|
||||||
|
body = "On Android, updates happen automatically. On Arch Linux, you need to run updates manually — and there's an important rule: always update ALL apps at the same time, never just the list. In a terminal, type: sudo pacman -Syu — then press Enter. Enter your password when asked. The -Syu means 'sync the list AND upgrade everything.'"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pacman-dep-conflict"
|
||||||
|
sources = ["journald", "applog:pacman"]
|
||||||
|
match_text = "conflicting dependencies"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Two apps conflict with each other"
|
||||||
|
body = "Two packages need something that can't be shared — like two apps that both want to be the default music player. Read the message carefully. Usually one package replaces another. Remove the old one first: sudo pacman -R <old-package-name> — then try your install again."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "aur-build-failure"
|
||||||
|
sources = ["journald", "applog:pacman"]
|
||||||
|
match_text = "error: failed to build"
|
||||||
|
severity = "warn"
|
||||||
|
title = "App build failed (AUR)"
|
||||||
|
body = "The AUR is like sideloading apps on Android — you're installing from source code that gets compiled on your machine, not a pre-built app. The build failed, usually because of a missing tool or broken code. Look at the error text above this message for the specific reason. The AUR package's comments page on aur.archlinux.org often has fixes."
|
||||||
|
|
||||||
|
# ── Terminal basics ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "command-not-found"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "command not found"
|
||||||
|
severity = "info"
|
||||||
|
title = "Command not found — app may not be installed"
|
||||||
|
body = "You tried to run a program that isn't installed. On Android, apps are visible in the drawer; on Linux, they're invisible until you ask for them. To find and install the missing program, try: sudo pacman -Ss <name> — this searches for it. Then install with: sudo pacman -S <name>."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "permission-denied"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Permission denied"
|
||||||
|
severity = "info"
|
||||||
|
title = "Permission denied"
|
||||||
|
body = "Linux has a permission system where files and folders are owned by specific users. This is more visible here than on Android. If you need admin access for a command, put 'sudo' before it — like: sudo <command>. For files you own but can't access, check ownership with: ls -la /path/to/file"
|
||||||
|
|
||||||
|
# ── System ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "kernel-driver-firmware"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "firmware: failed to load"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Hardware driver file missing"
|
||||||
|
body = "On Android, drivers come pre-installed and invisible. On Linux, some hardware needs a separate firmware file — like a plugin for your Wi-Fi chip or graphics card. Install the main firmware package: sudo pacman -S linux-firmware — then restart. If a specific device still doesn't work, the error message above will name which firmware file is missing."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "oom-killer"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Out of memory: Kill process"
|
||||||
|
severity = "warn"
|
||||||
|
title = "System ran out of memory — closed an app"
|
||||||
|
body = "Linux ran out of RAM and had to close a program, similar to Android killing background apps. If this keeps happening, close programs you're not using, or add 'swap' (disk space used as overflow RAM): sudo pacman -S zram-generator"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "disk-io-error"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Buffer I/O error on device"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Storage error"
|
||||||
|
body = "Something went wrong reading or writing to the drive. This could be a hardware problem. Check drive health: sudo smartctl -a /dev/sda — first install the tool: sudo pacman -S smartmontools"
|
||||||
|
|
||||||
|
# ── Audio ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pipewire-connect-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Failed to connect to PipeWire"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Sound system not responding"
|
||||||
|
body = "PipeWire is the audio manager — like the sound settings system inside Android. Restart it: open a terminal and type: systemctl --user restart pipewire pipewire-pulse wireplumber — if sound still doesn't work, log out and log back in."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "bluetooth-rfkill-blocked"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Blocked through rfkill"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Bluetooth blocked by software switch"
|
||||||
|
body = "A software setting is blocking Bluetooth — like Airplane Mode on your phone. Run: rfkill unblock bluetooth — in a terminal. If it shows 'Hard blocked', there's a physical switch or BIOS setting to check."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "bluetooth-profile-unavailable"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "br-connection-profile-unavailable"
|
||||||
|
severity = "info"
|
||||||
|
title = "Bluetooth audio profile missing"
|
||||||
|
body = "Your Bluetooth device connected but the right audio mode isn't available. Install: sudo pacman -S pipewire-bluetooth — then restart Bluetooth: sudo systemctl restart bluetooth"
|
||||||
|
|
||||||
|
# ── Network ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "networkmanager-activation-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Activation failed"
|
||||||
|
severity = "info"
|
||||||
|
title = "Wi-Fi connection failed"
|
||||||
|
body = "Linux couldn't connect to the network. Common causes: wrong password, or the Wi-Fi driver isn't loaded. Check connection status: nmcli device status — if the Wi-Fi adapter doesn't appear, the driver may need to be installed."
|
||||||
|
|
||||||
|
# ── GPU ───────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "gpu-hang"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "GPU HANG"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Graphics card stopped responding"
|
||||||
|
body = "The graphics card froze and the driver recovered it — like a forced restart of the GPU. Games or video apps may have crashed. If this keeps happening, check that your graphics drivers are current: sudo pacman -Syu"
|
||||||
137
src-tauri/patterns/android-to-debian.toml
Normal file
137
src-tauri/patterns/android-to-debian.toml
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
[meta]
|
||||||
|
source_os = "android"
|
||||||
|
target_distro_family = "debian"
|
||||||
|
|
||||||
|
# Android user on their first Debian/Ubuntu/Mint install.
|
||||||
|
# Assumes NO terminal experience. Ubuntu/Mint are the recommended starting points
|
||||||
|
# for Android migrants because of automatic updates and GUI app stores (GNOME Software/Discover).
|
||||||
|
|
||||||
|
[log_paths]
|
||||||
|
steam = "~/.local/share/Steam/logs/content_log.txt"
|
||||||
|
proton = "~/.local/share/Steam/logs/proton_log.txt"
|
||||||
|
|
||||||
|
# ── Package management ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "apt-lock"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Could not get lock /var/lib/dpkg/lock"
|
||||||
|
severity = "warn"
|
||||||
|
title = "App installer is busy"
|
||||||
|
body = "The software installer (Ubuntu/Mint calls it 'apt') is already running — probably doing automatic background updates, similar to how Android apps update silently. Wait a minute and try again. If it's stuck: open a terminal and type: 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 = "App install was cut short"
|
||||||
|
body = "A previous install didn't finish cleanly — like pulling the charging cable out mid-update on your phone. Fix it: open a terminal and type: sudo dpkg --configure -a — then try your install again."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "apt-unmet-dependency"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Unmet dependencies"
|
||||||
|
severity = "warn"
|
||||||
|
title = "App needs another app first"
|
||||||
|
body = "The software you're trying to install needs something else installed first — similar to a game on Android requiring Google Play Services. Let the installer fix it automatically: sudo apt --fix-broken install"
|
||||||
|
|
||||||
|
# ── Terminal basics ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "permission-denied"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Permission denied"
|
||||||
|
severity = "info"
|
||||||
|
title = "Permission denied"
|
||||||
|
body = "Linux files and folders are protected by a permission system. If a command fails with this error, you may need to run it as admin — put 'sudo' before the command: sudo <command> — and enter your password. Your password won't show as you type, that's normal."
|
||||||
|
|
||||||
|
# ── AppArmor ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "apparmor-denial"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "apparmor=\"DENIED\""
|
||||||
|
severity = "info"
|
||||||
|
title = "App blocked by security policy"
|
||||||
|
body = "Ubuntu/Debian includes a security layer called AppArmor — like Android's app permissions system, but for the whole operating system. An app tried to do something outside its allowed permissions. Usually this resolves itself; if an app keeps failing, check: sudo aa-status"
|
||||||
|
|
||||||
|
# ── System ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "kernel-driver-firmware"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "firmware: failed to load"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Hardware driver file missing"
|
||||||
|
body = "Some hardware needs a 'firmware' file — a small program that tells Linux how to talk to a specific chip. Install the main firmware package: sudo apt install firmware-linux linux-firmware — restart after. Ubuntu usually handles this automatically; you may see this on Debian."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "oom-killer"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Out of memory: Kill process"
|
||||||
|
severity = "warn"
|
||||||
|
title = "System ran out of memory — closed an app"
|
||||||
|
body = "Linux had to close a program to free up memory — similar to Android killing background apps when RAM is full. If this keeps happening, try closing programs you're not using."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "disk-io-error"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Buffer I/O error on device"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Storage error"
|
||||||
|
body = "Something went wrong reading or writing to the drive. This is a hardware-level issue. Install a diagnostic tool: sudo apt install smartmontools — then check: sudo smartctl -a /dev/sda"
|
||||||
|
|
||||||
|
# ── Audio ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pipewire-connect-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Failed to connect to PipeWire"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Sound system not responding"
|
||||||
|
body = "PipeWire is the audio manager — restart it: systemctl --user restart pipewire pipewire-pulse wireplumber — if that doesn't help, log out and back in."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pulseaudio-connect-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Failed to connect to pulseaudio"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Sound system not responding"
|
||||||
|
body = "The audio system (PulseAudio) stopped working. Restart it: pulseaudio --kill && pulseaudio --start — or log out and back in."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "bluetooth-rfkill-blocked"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Blocked through rfkill"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Bluetooth blocked by software switch"
|
||||||
|
body = "Run: rfkill unblock bluetooth — in a terminal. Like turning Airplane Mode off on your phone."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "cups-server-error"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Unable to connect to CUPS server"
|
||||||
|
severity = "info"
|
||||||
|
title = "Printer service not running"
|
||||||
|
body = "The printing service isn't running. Unlike Android where you'd use a manufacturer app, Linux uses a universal print system called CUPS. Start it: sudo systemctl start cups && sudo systemctl enable cups"
|
||||||
|
|
||||||
|
# ── Network ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "networkmanager-activation-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Activation failed"
|
||||||
|
severity = "info"
|
||||||
|
title = "Wi-Fi connection failed"
|
||||||
|
body = "Couldn't connect to the network. Double-check the password, or try: nmcli device status — in a terminal to see your network devices."
|
||||||
|
|
||||||
|
# ── Media ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "missing-codec"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "GStreamer: Failed to find plugin"
|
||||||
|
severity = "info"
|
||||||
|
title = "Media format not supported"
|
||||||
|
body = "Linux doesn't include some video/audio formats by default for legal reasons — unlike Android which bundles them. Install them on Ubuntu/Mint: sudo apt install ubuntu-restricted-extras — this adds MP3, MP4, and other common formats."
|
||||||
102
src-tauri/patterns/android-to-fedora.toml
Normal file
102
src-tauri/patterns/android-to-fedora.toml
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
[meta]
|
||||||
|
source_os = "android"
|
||||||
|
target_distro_family = "fedora"
|
||||||
|
|
||||||
|
# Android user on their first Fedora install.
|
||||||
|
# Assumes NO terminal experience. All explanations from first principles.
|
||||||
|
|
||||||
|
[log_paths]
|
||||||
|
steam = "~/.local/share/Steam/logs/content_log.txt"
|
||||||
|
proton = "~/.local/share/Steam/logs/proton_log.txt"
|
||||||
|
|
||||||
|
# ── Package management ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "dnf-lock"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Another app is currently holding the dnf lock"
|
||||||
|
severity = "warn"
|
||||||
|
title = "App installer is busy"
|
||||||
|
body = "Fedora's software installer (dnf) is already running — probably automatic background updates, similar to how Android apps update silently. Wait a minute. If it's stuck: open a terminal and type: sudo killall dnf — then try again."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "dnf-dep-conflict"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "conflicts with"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Two apps conflict with each other"
|
||||||
|
body = "Two packages need something that can't be shared. Let Fedora try to fix it: sudo dnf distro-sync — this brings everything back into a consistent state."
|
||||||
|
|
||||||
|
# ── SELinux ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "selinux-denial"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "type=AVC"
|
||||||
|
severity = "info"
|
||||||
|
title = "Security system blocked an action"
|
||||||
|
body = "Fedora includes SELinux — a security layer that controls what each program is allowed to do, more detailed than Android's app permissions. This is usually a normal event, not a problem. If an app keeps failing, check what's being blocked: ausearch -m AVC -ts recent"
|
||||||
|
|
||||||
|
# ── System ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "kernel-driver-firmware"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "firmware: failed to load"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Hardware driver file missing"
|
||||||
|
body = "Some hardware needs a firmware file — a program that tells Linux how to talk to a specific chip. Install it: sudo dnf install linux-firmware — restart after. For some hardware (especially older WiFi cards), you may also need: 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 = "System ran out of memory — closed an app"
|
||||||
|
body = "Linux had to close a program to free up RAM, like Android killing background apps. If this keeps happening, try closing some programs."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "disk-io-error"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Buffer I/O error on device"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Storage error"
|
||||||
|
body = "A hardware-level storage error. Install a diagnostic tool: sudo dnf install smartmontools — then check: sudo smartctl -a /dev/sda"
|
||||||
|
|
||||||
|
# ── Audio ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pipewire-connect-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Failed to connect to PipeWire"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Sound system not responding"
|
||||||
|
body = "Restart the audio system: systemctl --user restart pipewire pipewire-pulse wireplumber — or log out and back in."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "bluetooth-rfkill-blocked"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Blocked through rfkill"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Bluetooth blocked by software switch"
|
||||||
|
body = "Run: rfkill unblock bluetooth — in a terminal."
|
||||||
|
|
||||||
|
# ── Network ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "networkmanager-activation-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Activation failed"
|
||||||
|
severity = "info"
|
||||||
|
title = "Wi-Fi connection failed"
|
||||||
|
body = "Couldn't connect to the network. Check: nmcli device status — in a terminal. If the adapter doesn't appear, check: dmesg | grep firmware — a missing driver may be the cause."
|
||||||
|
|
||||||
|
# ── Media ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "missing-codec"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "GStreamer: Failed to find plugin"
|
||||||
|
severity = "info"
|
||||||
|
title = "Media format not supported"
|
||||||
|
body = "Linux doesn't include some video/audio formats by default. Install them from RPM Fusion: first enable it: sudo dnf install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm — then: sudo dnf install gstreamer1-plugins-bad-free gstreamer1-plugins-ugly"
|
||||||
103
src-tauri/patterns/android-to-opensuse.toml
Normal file
103
src-tauri/patterns/android-to-opensuse.toml
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
[meta]
|
||||||
|
source_os = "android"
|
||||||
|
target_distro_family = "opensuse"
|
||||||
|
|
||||||
|
# Android user on their first openSUSE install.
|
||||||
|
# Assumes NO terminal experience. openSUSE's YaST GUI tool is a good entry point
|
||||||
|
# for users unfamiliar with terminal-based administration.
|
||||||
|
|
||||||
|
[log_paths]
|
||||||
|
steam = "~/.local/share/Steam/logs/content_log.txt"
|
||||||
|
proton = "~/.local/share/Steam/logs/proton_log.txt"
|
||||||
|
|
||||||
|
# ── Package management ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "zypper-lock"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "System management is locked"
|
||||||
|
severity = "warn"
|
||||||
|
title = "App installer is busy"
|
||||||
|
body = "The software installer (zypper) is already running. Wait a minute. If it's stuck: open a terminal and type: sudo zypper ps — to see what's using it."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "zypper-dep-conflict"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "conflicts with"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Two apps conflict with each other"
|
||||||
|
body = "Two packages need something that can't be shared. Run a full update to resolve: sudo zypper dup — then try again."
|
||||||
|
|
||||||
|
# ── AppArmor ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "apparmor-denial"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "apparmor=\"DENIED\""
|
||||||
|
severity = "info"
|
||||||
|
title = "App blocked by security policy"
|
||||||
|
body = "openSUSE includes AppArmor — a security layer like Android's app permissions but for the whole system. An app was blocked from doing something. Check: sudo aa-status — YaST also has an AppArmor section under Security."
|
||||||
|
|
||||||
|
# ── YaST ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "yast-backend-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "YaST got signal"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Settings tool crashed"
|
||||||
|
body = "YaST is openSUSE's settings tool — like the Settings app on Android but for the whole system. It crashed. Try: sudo yast2 — in a terminal to run the text version, which is more stable."
|
||||||
|
|
||||||
|
# ── System ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "kernel-driver-firmware"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "firmware: failed to load"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Hardware driver file missing"
|
||||||
|
body = "Install the firmware package: sudo zypper install kernel-firmware — restart after."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "oom-killer"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Out of memory: Kill process"
|
||||||
|
severity = "warn"
|
||||||
|
title = "System ran out of memory — closed an app"
|
||||||
|
body = "Linux had to close a program to free up RAM, like Android killing background apps. If this keeps happening, openSUSE's YaST -> System -> Partitioner can add or resize swap space."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "disk-io-error"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Buffer I/O error on device"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Storage error"
|
||||||
|
body = "A hardware-level storage error. Install: sudo zypper install smartmontools — then check: sudo smartctl -a /dev/sda"
|
||||||
|
|
||||||
|
# ── Audio ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pipewire-connect-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Failed to connect to PipeWire"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Sound system not responding"
|
||||||
|
body = "Restart the audio system: systemctl --user restart pipewire pipewire-pulse wireplumber — or log out and back in."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "bluetooth-rfkill-blocked"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Blocked through rfkill"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Bluetooth blocked by software switch"
|
||||||
|
body = "Run: rfkill unblock bluetooth — in a terminal. YaST -> Network -> Bluetooth also has a toggle."
|
||||||
|
|
||||||
|
# ── Network ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "networkmanager-activation-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Activation failed"
|
||||||
|
severity = "info"
|
||||||
|
title = "Wi-Fi connection failed"
|
||||||
|
body = "Couldn't connect to the network. Check: nmcli device status — or use YaST -> Network Settings to diagnose."
|
||||||
65
src-tauri/patterns/dualboot-macos.toml
Normal file
65
src-tauri/patterns/dualboot-macos.toml
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
[meta]
|
||||||
|
source_os = "supplement"
|
||||||
|
target_distro_family = "any"
|
||||||
|
|
||||||
|
# Supplementary patterns for users dual-booting macOS alongside any Linux distro.
|
||||||
|
# These patterns cover coexistence-specific issues unique to Apple hardware.
|
||||||
|
# This file is merged on top of the primary migration pattern file.
|
||||||
|
|
||||||
|
[log_paths]
|
||||||
|
|
||||||
|
# ── Apple T2 / Secure Boot ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "t2-secure-boot"
|
||||||
|
sources = ["kmsg", "journald"]
|
||||||
|
match_text = "Secure Boot"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Apple T2 Secure Boot blocking Linux"
|
||||||
|
body = "Intel Macs with a T2 chip require Secure Boot to be disabled before Linux can boot. Boot into macOS Recovery (hold Cmd+R at startup) -> Utilities -> Startup Security Utility -> set Secure Boot to 'No Security' and allow booting from external media. Apple Silicon (M1/M2) Macs cannot dual-boot Linux at all — see Asahi Linux for the current state."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "apple-wifi-firmware"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "brcmfmac: brcmf_fw_alloc_request"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Apple WiFi firmware not loading"
|
||||||
|
body = "Broadcom WiFi chips in Macs need proprietary firmware. Extract it from the macOS partition: mount your macOS partition and copy from /Volumes/Macintosh HD/usr/share/firmware/wifi/ — or install apple-firmware-wifi (check your distro's AUR or repos)."
|
||||||
|
|
||||||
|
# ── HFS+ / APFS ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "apfs-not-mounted"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "apfs: module not found"
|
||||||
|
severity = "info"
|
||||||
|
title = "macOS APFS partition not readable"
|
||||||
|
body = "Linux can't read APFS (macOS's filesystem) natively. To access files: sudo apt install apfs-fuse (Debian) or paru -S apfs-fuse-git (Arch). Mount: apfs-fuse /dev/sdXN /mnt/mac — read-only access only."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "hfsplus-not-mounted"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "hfsplus: Journal not clean"
|
||||||
|
severity = "warn"
|
||||||
|
title = "HFS+ partition not cleanly unmounted"
|
||||||
|
body = "The macOS HFS+ partition (older Macs) wasn't unmounted cleanly. Mount in macOS and run Disk Utility -> First Aid to fix it. Or force Linux mount: sudo mount -o force /dev/sdXN /mnt/mac"
|
||||||
|
|
||||||
|
# ── rEFInd / boot manager ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "refind-missing"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Boot0001"
|
||||||
|
severity = "info"
|
||||||
|
title = "EFI boot entry may be missing"
|
||||||
|
body = "macOS may have reset the EFI boot order after an update, removing the Linux entry. rEFInd is the recommended boot manager for Mac dual-boot: it auto-detects both macOS and Linux. Install: sudo refind-install — or reinstall GRUB EFI and re-add it with efibootmgr."
|
||||||
|
|
||||||
|
# ── Clock skew ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "rtc-time-wrong"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "RTC time"
|
||||||
|
severity = "info"
|
||||||
|
title = "System clock drifted after macOS boot"
|
||||||
|
body = "macOS stores the hardware clock in local time; Linux stores it in UTC. This causes clock drift in dual-boot. Fix in Linux: timedatectl set-local-rtc 0 — then set macOS to UTC by running in Terminal: sudo systemsetup -setusingnetworktime off && sudo systemsetup -settime $(date -u +%H:%M:%S)"
|
||||||
75
src-tauri/patterns/dualboot-windows.toml
Normal file
75
src-tauri/patterns/dualboot-windows.toml
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
[meta]
|
||||||
|
source_os = "supplement"
|
||||||
|
target_distro_family = "any"
|
||||||
|
|
||||||
|
# Supplementary patterns for users dual-booting Windows alongside any Linux distro.
|
||||||
|
# These patterns cover coexistence-specific issues that only appear because both OSes
|
||||||
|
# share the same hardware. This file is merged on top of the primary migration pattern file.
|
||||||
|
|
||||||
|
[log_paths]
|
||||||
|
|
||||||
|
# ── NTFS / Fast Startup ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "ntfs-volume-dirty"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "volume is dirty"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Windows drive needs a check (Fast Startup)"
|
||||||
|
body = "Windows didn't shut down cleanly — it probably used Fast Startup (hibernation). Linux mounted the NTFS partition read-only to protect your data. Fix in Windows: Start -> Power -> hold Shift and click Shut Down (real shutdown, not hibernate). Then turn Fast Startup off: Control Panel -> Power Options -> 'Choose what the power buttons do' -> uncheck 'Turn on fast startup'."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "ntfs-force-required"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Dirty flag is set"
|
||||||
|
severity = "warn"
|
||||||
|
title = "NTFS drive mounted read-only (dirty flag)"
|
||||||
|
body = "Windows left the NTFS filesystem marked dirty. Boot into Windows and do a full shutdown (Shift+Shut Down), or force-fix on Linux: sudo ntfsfix /dev/sdXN — replace sdXN with the partition shown in the error."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "ntfs-hibernation"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Windows is hibernated"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Windows is hibernated — partition locked"
|
||||||
|
body = "Linux found a Windows hibernation file (hiberfil.sys) and can't write to the NTFS partition safely. You must resume and properly shut down Windows first. To remove the hibernation file permanently: in Windows as admin, run: powercfg /h off"
|
||||||
|
|
||||||
|
# ── Clock skew ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "rtc-time-wrong"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "RTC time"
|
||||||
|
severity = "info"
|
||||||
|
title = "System clock drifted after Windows boot"
|
||||||
|
body = "Windows stores the hardware clock in local time; Linux stores it in UTC. Dual-booting causes clock drift between sessions. Fix permanently in Linux: timedatectl set-local-rtc 0 (keep Linux correct and fix Windows instead). Or in Windows, add a registry key to use UTC: HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation, add DWORD RealTimeIsUniversal = 1."
|
||||||
|
|
||||||
|
# ── GRUB overwritten by Windows ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "grub-missing-windows-update"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "error: no such device"
|
||||||
|
severity = "warn"
|
||||||
|
title = "GRUB may have been overwritten"
|
||||||
|
body = "Windows Update sometimes overwrites the EFI boot entry. If Linux stopped booting after a Windows update: boot from your Linux USB installer -> rescue/chroot -> reinstall GRUB: grub-install /dev/sdX && update-grub (Debian/Ubuntu) or grub-install /dev/sdX && grub-mkconfig -o /boot/grub/grub.cfg (Arch)."
|
||||||
|
|
||||||
|
# ── BitLocker ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "bitlocker-blocked"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "BitLocker"
|
||||||
|
severity = "info"
|
||||||
|
title = "BitLocker encrypted partition"
|
||||||
|
body = "This Windows partition is BitLocker-encrypted. Linux can mount it with dislocker: sudo apt install dislocker (Debian) or paru -S dislocker (Arch). You'll need the BitLocker recovery key from your Microsoft account."
|
||||||
|
|
||||||
|
# ── Shared NTFS partition permissions ────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "ntfs-permission-error"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "ntfs-3g: Failed to open"
|
||||||
|
severity = "warn"
|
||||||
|
title = "NTFS permission error"
|
||||||
|
body = "ntfs-3g can't open the Windows partition. Check your /etc/fstab mount options — add uid=1000,gid=1000,umask=022 to give your Linux user access. Make sure Windows is fully shut down first."
|
||||||
120
src-tauri/patterns/ipad-to-arch.toml
Normal file
120
src-tauri/patterns/ipad-to-arch.toml
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
[meta]
|
||||||
|
source_os = "ipad"
|
||||||
|
target_distro_family = "arch"
|
||||||
|
|
||||||
|
# iPad/iPhone user on their first Arch/CachyOS install.
|
||||||
|
# More sandboxed mental model than Android — no file manager, no sideloading concept,
|
||||||
|
# everything lived inside apps. Arch is a steep starting point; body text is encouraging
|
||||||
|
# but honest about the learning curve.
|
||||||
|
|
||||||
|
[log_paths]
|
||||||
|
steam = "~/.local/share/Steam/logs/content_log.txt"
|
||||||
|
proton = "~/.local/share/Steam/logs/proton_log.txt"
|
||||||
|
|
||||||
|
# ── Package management ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pacman-db-lock"
|
||||||
|
sources = ["journald", "applog:pacman"]
|
||||||
|
match_text = "could not lock database: File exists"
|
||||||
|
severity = "warn"
|
||||||
|
title = "App installer is busy"
|
||||||
|
body = "The package manager (pacman — Linux's version of the App Store, but text-based) got interrupted and left a lock file behind. Think of it like an App Store update that got cut off. If nothing is currently installing, remove the lock: open a terminal (called 'Konsole' or 'Alacritty' on your system) and type: sudo rm /var/lib/pacman/db.lck — then press Enter. 'sudo' means run as administrator; your password won't show as you type."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "partial-upgrade-warning"
|
||||||
|
sources = ["applog:pacman"]
|
||||||
|
match_text = "warning: database file for"
|
||||||
|
severity = "info"
|
||||||
|
title = "App list out of date — update everything together"
|
||||||
|
body = "On iPad, updates happen automatically and silently. On Arch Linux, you run them manually. Important rule: always update everything at once. In a terminal, type: sudo pacman -Syu — press Enter, enter your password. This refreshes the app list AND updates all installed software."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "aur-build-failure"
|
||||||
|
sources = ["journald", "applog:pacman"]
|
||||||
|
match_text = "error: failed to build"
|
||||||
|
severity = "warn"
|
||||||
|
title = "App build failed')"
|
||||||
|
body = "The AUR (Arch User Repository) is software that gets compiled on your machine from source code — there's no real iOS equivalent since Apple controls all distribution. A build failed. Look at the error text above this notification for the specific cause. The AUR package's comment page on aur.archlinux.org often has workarounds."
|
||||||
|
|
||||||
|
# ── Files and paths ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "permission-denied"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Permission denied"
|
||||||
|
severity = "info"
|
||||||
|
title = "Permission denied"
|
||||||
|
body = "On iPad, every app lives in its own private sandbox — you never think about file permissions. On Linux, files are shared between users and programs, and access is controlled by permissions. If you need admin rights for a command, add 'sudo' before it: sudo <command> — and enter your password."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "no-such-file"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "No such file or directory"
|
||||||
|
severity = "info"
|
||||||
|
title = "File or folder not found"
|
||||||
|
body = "On iPad, files lived inside apps and you never typed paths. On Linux, files have locations like /home/username/Documents. Check that the path you typed is correct — Linux paths are case-sensitive ('Documents' and 'documents' are different). Use 'ls' to list files in the current folder."
|
||||||
|
|
||||||
|
# ── System ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "kernel-driver-firmware"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "firmware: failed to load"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Hardware driver file missing"
|
||||||
|
body = "On iPad, Apple controls all hardware and drivers — you never see this. On Linux, some hardware components need a driver file installed separately. Install the main driver package: sudo pacman -S linux-firmware — then restart your computer."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "oom-killer"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Out of memory: Kill process"
|
||||||
|
severity = "warn"
|
||||||
|
title = "System ran out of memory — closed an app"
|
||||||
|
body = "Linux had to close a program to free up RAM — similar to iPadOS refreshing apps in the background when RAM runs low. If this keeps happening, consider closing unused programs or adding 'zram' (uses storage as extra RAM): sudo pacman -S zram-generator"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "disk-io-error"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Buffer I/O error on device"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Storage error"
|
||||||
|
body = "Something went wrong with the drive. Install a check tool: sudo pacman -S smartmontools — then run: sudo smartctl -a /dev/sda"
|
||||||
|
|
||||||
|
# ── Audio ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pipewire-connect-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Failed to connect to PipeWire"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Sound system not responding"
|
||||||
|
body = "PipeWire manages audio on this system — like the iOS audio system, but you can restart it. Type in a terminal: systemctl --user restart pipewire pipewire-pulse wireplumber — if sound still doesn't work, log out and log back in."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "bluetooth-rfkill-blocked"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Blocked through rfkill"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Bluetooth turned off by software"
|
||||||
|
body = "A software setting is blocking Bluetooth — like enabling Airplane Mode. Turn it back on: rfkill unblock bluetooth — in a terminal. If it says 'Hard blocked', there's a physical switch on your computer or a setting in the BIOS."
|
||||||
|
|
||||||
|
# ── Network ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "networkmanager-activation-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Activation failed"
|
||||||
|
severity = "info"
|
||||||
|
title = "Wi-Fi connection failed"
|
||||||
|
body = "Couldn't connect to the network. Check status: nmcli device status — in a terminal. If the Wi-Fi adapter doesn't appear in the list, the driver may not be loaded."
|
||||||
|
|
||||||
|
# ── GPU ───────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "gpu-hang"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "GPU HANG"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Graphics card stopped responding"
|
||||||
|
body = "The graphics system crashed and recovered — similar to an app freezing on iPad, but at a lower level. If this keeps happening during games or video, update your graphics drivers: sudo pacman -Syu"
|
||||||
137
src-tauri/patterns/ipad-to-debian.toml
Normal file
137
src-tauri/patterns/ipad-to-debian.toml
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
[meta]
|
||||||
|
source_os = "ipad"
|
||||||
|
target_distro_family = "debian"
|
||||||
|
|
||||||
|
# iPad/iPhone user on their first Debian/Ubuntu/Mint install.
|
||||||
|
# Ubuntu/Mint are the most recommended starting points for iPad migrants —
|
||||||
|
# automatic updates, GUI software center, familiar GNOME/Cinnamon interface.
|
||||||
|
|
||||||
|
[log_paths]
|
||||||
|
steam = "~/.local/share/Steam/logs/content_log.txt"
|
||||||
|
proton = "~/.local/share/Steam/logs/proton_log.txt"
|
||||||
|
|
||||||
|
# ── Package management ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "apt-lock"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Could not get lock /var/lib/dpkg/lock"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Software installer is busy"
|
||||||
|
body = "Ubuntu's software installer is already running in the background — probably doing automatic updates, similar to how iOS updates apps silently. Wait a minute and try again. You can also open 'Software Updater' from the app menu to see what's happening. If it's been stuck for a long time: open the Terminal app and type: sudo rm /var/lib/dpkg/lock-frontend && sudo dpkg --configure -a"
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "dpkg-interrupted"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "dpkg was interrupted"
|
||||||
|
severity = "warn"
|
||||||
|
title = "App install was cut short"
|
||||||
|
body = "A previous software install didn't finish. Fix it: open Terminal and type: sudo dpkg --configure -a — then press Enter."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "apt-unmet-dependency"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Unmet dependencies"
|
||||||
|
severity = "warn"
|
||||||
|
title = "App needs another app first"
|
||||||
|
body = "The software you're installing needs something else first. Let Ubuntu fix it: sudo apt --fix-broken install"
|
||||||
|
|
||||||
|
# ── AppArmor ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "apparmor-denial"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "apparmor=\"DENIED\""
|
||||||
|
severity = "info"
|
||||||
|
title = "App blocked by security policy"
|
||||||
|
body = "Ubuntu includes AppArmor — a security layer that restricts what each program can do, similar to how iOS tightly sandboxes every app. Something was blocked. This is usually routine security protection, not a problem."
|
||||||
|
|
||||||
|
# ── Files and paths ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "permission-denied"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Permission denied"
|
||||||
|
severity = "info"
|
||||||
|
title = "Permission denied"
|
||||||
|
body = "On iPad, files are hidden inside apps and permissions are invisible. On Linux, files are shared and controlled. If a command fails with this, add 'sudo' before it to run as admin: sudo <command> — your password won't show as you type, that's normal."
|
||||||
|
|
||||||
|
# ── System ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "kernel-driver-firmware"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "firmware: failed to load"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Hardware driver file missing"
|
||||||
|
body = "Some hardware needs a driver file installed separately — unlike iPad where Apple controls everything. Ubuntu usually handles this, but if something isn't working: sudo apt install firmware-linux linux-firmware — then restart."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "oom-killer"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Out of memory: Kill process"
|
||||||
|
severity = "warn"
|
||||||
|
title = "System ran out of memory"
|
||||||
|
body = "Linux closed a program to free up RAM — similar to iPadOS refreshing apps in the background. Try closing some programs you're not using."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "disk-io-error"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Buffer I/O error on device"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Storage error"
|
||||||
|
body = "A hardware-level storage error. Install: sudo apt install smartmontools — then check: sudo smartctl -a /dev/sda"
|
||||||
|
|
||||||
|
# ── Audio ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pipewire-connect-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Failed to connect to PipeWire"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Sound system not responding"
|
||||||
|
body = "Restart audio: systemctl --user restart pipewire pipewire-pulse wireplumber — or log out and back in."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pulseaudio-connect-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Failed to connect to pulseaudio"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Sound system not responding"
|
||||||
|
body = "Restart audio: pulseaudio --kill && pulseaudio --start — or log out and back in."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "bluetooth-rfkill-blocked"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Blocked through rfkill"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Bluetooth turned off by software"
|
||||||
|
body = "Run: rfkill unblock bluetooth — in a terminal."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "cups-server-error"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Unable to connect to CUPS server"
|
||||||
|
severity = "info"
|
||||||
|
title = "Printer service not running"
|
||||||
|
body = "The printing service needs to be started: sudo systemctl start cups && sudo systemctl enable cups — then try printing again from your app."
|
||||||
|
|
||||||
|
# ── Network ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "networkmanager-activation-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Activation failed"
|
||||||
|
severity = "info"
|
||||||
|
title = "Wi-Fi connection failed"
|
||||||
|
body = "Check the network status icon in the top bar — or open Terminal and type: nmcli device status"
|
||||||
|
|
||||||
|
# ── Media ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "missing-codec"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "GStreamer: Failed to find plugin"
|
||||||
|
severity = "info"
|
||||||
|
title = "Video or audio format not supported"
|
||||||
|
body = "Linux needs extra packages to play some media formats. On Ubuntu/Mint: sudo apt install ubuntu-restricted-extras — this adds support for MP3, MP4, and other common formats."
|
||||||
102
src-tauri/patterns/ipad-to-fedora.toml
Normal file
102
src-tauri/patterns/ipad-to-fedora.toml
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
[meta]
|
||||||
|
source_os = "ipad"
|
||||||
|
target_distro_family = "fedora"
|
||||||
|
|
||||||
|
# iPad/iPhone user on their first Fedora install.
|
||||||
|
# Assumes NO terminal experience. Fedora GNOME is visually close to iPadOS.
|
||||||
|
|
||||||
|
[log_paths]
|
||||||
|
steam = "~/.local/share/Steam/logs/content_log.txt"
|
||||||
|
proton = "~/.local/share/Steam/logs/proton_log.txt"
|
||||||
|
|
||||||
|
# ── Package management ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "dnf-lock"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Another app is currently holding the dnf lock"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Software installer is busy"
|
||||||
|
body = "Fedora is doing automatic updates in the background — like iOS updating apps silently. Wait a minute and try again. You can see what's happening in GNOME Software (the App Store equivalent)."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "dnf-dep-conflict"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "conflicts with"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Two apps conflict with each other"
|
||||||
|
body = "Two packages need something that can't coexist. Run: sudo dnf distro-sync — in a terminal to bring everything back into sync."
|
||||||
|
|
||||||
|
# ── SELinux ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "selinux-denial"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "type=AVC"
|
||||||
|
severity = "info"
|
||||||
|
title = "Security system blocked an action"
|
||||||
|
body = "Fedora uses SELinux — a detailed security system that controls what each program can access. Think of it as a much stricter version of iOS app permissions. This log entry is usually routine. If an app keeps failing, GNOME's 'Setroubleshoot' tool will pop up with an explanation and fix."
|
||||||
|
|
||||||
|
# ── System ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "kernel-driver-firmware"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "firmware: failed to load"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Hardware driver file missing"
|
||||||
|
body = "Some hardware needs a driver installed separately. Fedora usually handles this automatically, but if a device isn't working: sudo dnf install linux-firmware — restart after."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "oom-killer"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Out of memory: Kill process"
|
||||||
|
severity = "warn"
|
||||||
|
title = "System ran out of memory"
|
||||||
|
body = "Linux closed a program to free RAM — like iPadOS refreshing background apps. Close unused programs."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "disk-io-error"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Buffer I/O error on device"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Storage error"
|
||||||
|
body = "Install: sudo dnf install smartmontools — then check: sudo smartctl -a /dev/sda"
|
||||||
|
|
||||||
|
# ── Audio ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pipewire-connect-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Failed to connect to PipeWire"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Sound system not responding"
|
||||||
|
body = "Restart audio: systemctl --user restart pipewire pipewire-pulse wireplumber — or log out and back in."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "bluetooth-rfkill-blocked"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Blocked through rfkill"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Bluetooth turned off by software"
|
||||||
|
body = "Run: rfkill unblock bluetooth — in a terminal."
|
||||||
|
|
||||||
|
# ── Network ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "networkmanager-activation-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Activation failed"
|
||||||
|
severity = "info"
|
||||||
|
title = "Wi-Fi connection failed"
|
||||||
|
body = "Check the network icon in the top bar, or: nmcli device status — in a terminal."
|
||||||
|
|
||||||
|
# ── Media ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "missing-codec"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "GStreamer: Failed to find plugin"
|
||||||
|
severity = "info"
|
||||||
|
title = "Video or audio format not supported"
|
||||||
|
body = "Fedora needs extra packages for some media formats. Enable RPM Fusion: sudo dnf install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm — then: sudo dnf install gstreamer1-plugins-bad-free gstreamer1-plugins-ugly"
|
||||||
111
src-tauri/patterns/ipad-to-opensuse.toml
Normal file
111
src-tauri/patterns/ipad-to-opensuse.toml
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
[meta]
|
||||||
|
source_os = "ipad"
|
||||||
|
target_distro_family = "opensuse"
|
||||||
|
|
||||||
|
# iPad/iPhone user on their first openSUSE install.
|
||||||
|
# YaST (openSUSE's graphical admin tool) is a good bridge for users
|
||||||
|
# unfamiliar with terminal-based system administration.
|
||||||
|
|
||||||
|
[log_paths]
|
||||||
|
steam = "~/.local/share/Steam/logs/content_log.txt"
|
||||||
|
proton = "~/.local/share/Steam/logs/proton_log.txt"
|
||||||
|
|
||||||
|
# ── Package management ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "zypper-lock"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "System management is locked"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Software installer is busy"
|
||||||
|
body = "openSUSE's software manager is running — like iOS doing background updates. Wait a minute, or open YaST -> Software -> Software Management to see what's happening."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "zypper-dep-conflict"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "conflicts with"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Two apps conflict"
|
||||||
|
body = "Run a full update: sudo zypper dup — this resolves most conflicts."
|
||||||
|
|
||||||
|
# ── AppArmor ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "apparmor-denial"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "apparmor=\"DENIED\""
|
||||||
|
severity = "info"
|
||||||
|
title = "App blocked by security policy"
|
||||||
|
body = "openSUSE uses AppArmor for security — similar to how iOS isolates apps. Something was blocked. YaST -> Security -> AppArmor Configuration shows active profiles."
|
||||||
|
|
||||||
|
# ── YaST ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "yast-backend-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "YaST got signal"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Settings tool crashed"
|
||||||
|
body = "YaST (openSUSE's settings tool) crashed. Try running it from a terminal: sudo yast2 — the text mode is more stable than the graphical version."
|
||||||
|
|
||||||
|
# ── System ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "kernel-driver-firmware"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "firmware: failed to load"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Hardware driver file missing"
|
||||||
|
body = "Install the firmware package: sudo zypper install kernel-firmware — restart after."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "oom-killer"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Out of memory: Kill process"
|
||||||
|
severity = "warn"
|
||||||
|
title = "System ran out of memory"
|
||||||
|
body = "Linux closed a program to free RAM. YaST -> System -> Partitioner can add or resize swap space (overflow RAM stored on disk)."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "disk-io-error"
|
||||||
|
sources = ["kmsg"]
|
||||||
|
match_text = "Buffer I/O error on device"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Storage error"
|
||||||
|
body = "Install: sudo zypper install smartmontools — then: sudo smartctl -a /dev/sda"
|
||||||
|
|
||||||
|
# ── Audio ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "pipewire-connect-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Failed to connect to PipeWire"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Sound system not responding"
|
||||||
|
body = "Restart audio: systemctl --user restart pipewire pipewire-pulse wireplumber — or log out and back in."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "bluetooth-rfkill-blocked"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Blocked through rfkill"
|
||||||
|
severity = "warn"
|
||||||
|
title = "Bluetooth turned off by software"
|
||||||
|
body = "Run: rfkill unblock bluetooth — or use YaST -> Network -> Bluetooth."
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "cups-server-error"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Unable to connect to CUPS server"
|
||||||
|
severity = "info"
|
||||||
|
title = "Printer service not running"
|
||||||
|
body = "Start printing: sudo systemctl start cups && sudo systemctl enable cups — or use YaST -> Hardware -> Printer."
|
||||||
|
|
||||||
|
# ── Network ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
[[patterns]]
|
||||||
|
id = "networkmanager-activation-fail"
|
||||||
|
sources = ["journald"]
|
||||||
|
match_text = "Activation failed"
|
||||||
|
severity = "info"
|
||||||
|
title = "Wi-Fi connection failed"
|
||||||
|
body = "Check YaST -> Network Settings — or: nmcli device status — in a terminal."
|
||||||
|
|
@ -29,12 +29,15 @@ pub fn complete_onboarding(
|
||||||
source_os: String,
|
source_os: String,
|
||||||
distro: String,
|
distro: String,
|
||||||
source_distro: Option<String>,
|
source_distro: Option<String>,
|
||||||
|
dual_boot_with: 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() {
|
||||||
"macos" | "mac" => SourceOs::Macos,
|
"macos" | "mac" => SourceOs::Macos,
|
||||||
"windows" => SourceOs::Windows,
|
"windows" => SourceOs::Windows,
|
||||||
"linux" => SourceOs::Linux,
|
"linux" => SourceOs::Linux,
|
||||||
|
"android" => SourceOs::Android,
|
||||||
|
"ipad" | "ios" | "ipados" => SourceOs::IpadOs,
|
||||||
_ => SourceOs::Unknown,
|
_ => SourceOs::Unknown,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -44,18 +47,24 @@ pub fn complete_onboarding(
|
||||||
distro
|
distro
|
||||||
};
|
};
|
||||||
|
|
||||||
// For Linux-to-Linux migrations, derive source family from the reported source distro.
|
let source_distro_family = source_distro.as_deref().and_then(|sd| {
|
||||||
// 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);
|
let family = crate::distro::distro_family(sd);
|
||||||
if family == "unknown" { None } else { Some(family.to_string()) }
|
if family == "unknown" { None } else { Some(family.to_string()) }
|
||||||
}).flatten();
|
});
|
||||||
|
|
||||||
|
// Normalise dual_boot_with to a canonical name; reject unrecognised values.
|
||||||
|
let dual_boot_with = dual_boot_with.and_then(|s| match s.to_lowercase().as_str() {
|
||||||
|
"windows" => Some("windows".to_string()),
|
||||||
|
"macos" | "mac" => Some("macos".to_string()),
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
|
||||||
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,
|
source_distro_family,
|
||||||
|
dual_boot_with,
|
||||||
fluency_level: 0,
|
fluency_level: 0,
|
||||||
});
|
});
|
||||||
config.save().map_err(|e| e.to_string())
|
config.save().map_err(|e| e.to_string())
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ pub enum SourceOs {
|
||||||
Macos,
|
Macos,
|
||||||
Windows,
|
Windows,
|
||||||
Linux,
|
Linux,
|
||||||
|
Android,
|
||||||
|
IpadOs,
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,6 +40,11 @@ pub struct MigrationConfig {
|
||||||
/// Used to load a more specific pattern file (e.g. debian-to-arch) before the generic linux-to-arch fallback.
|
/// Used to load a more specific pattern file (e.g. debian-to-arch) before the generic linux-to-arch fallback.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub source_distro_family: Option<String>,
|
pub source_distro_family: Option<String>,
|
||||||
|
/// Co-installed OS for dual-boot setups, e.g. "windows", "macos".
|
||||||
|
/// When set, a supplementary pattern file (dualboot-windows.toml) is loaded on top of
|
||||||
|
/// the primary migration patterns to surface coexistence-specific issues.
|
||||||
|
#[serde(default)]
|
||||||
|
pub dual_boot_with: 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,
|
||||||
}
|
}
|
||||||
|
|
@ -143,6 +150,7 @@ mod tests {
|
||||||
source_os: SourceOs::Macos,
|
source_os: SourceOs::Macos,
|
||||||
distro: "cachyos".into(),
|
distro: "cachyos".into(),
|
||||||
source_distro_family: None,
|
source_distro_family: None,
|
||||||
|
dual_boot_with: None,
|
||||||
fluency_level: 0,
|
fluency_level: 0,
|
||||||
});
|
});
|
||||||
assert!(!config.needs_onboarding());
|
assert!(!config.needs_onboarding());
|
||||||
|
|
@ -155,6 +163,7 @@ mod tests {
|
||||||
source_os: SourceOs::Windows,
|
source_os: SourceOs::Windows,
|
||||||
distro: "linuxmint".into(),
|
distro: "linuxmint".into(),
|
||||||
source_distro_family: None,
|
source_distro_family: None,
|
||||||
|
dual_boot_with: None,
|
||||||
fluency_level: 2,
|
fluency_level: 2,
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,18 @@ pub fn run() {
|
||||||
config::SourceOs::Macos => "macos",
|
config::SourceOs::Macos => "macos",
|
||||||
config::SourceOs::Windows => "windows",
|
config::SourceOs::Windows => "windows",
|
||||||
config::SourceOs::Linux => "linux",
|
config::SourceOs::Linux => "linux",
|
||||||
|
config::SourceOs::Android => "android",
|
||||||
|
config::SourceOs::IpadOs => "ipad",
|
||||||
config::SourceOs::Unknown => "unknown",
|
config::SourceOs::Unknown => "unknown",
|
||||||
};
|
};
|
||||||
patterns::load(source, migration.source_distro_family.as_deref(), family).ok()
|
let mut pf = patterns::load(source, migration.source_distro_family.as_deref(), family).ok();
|
||||||
|
// Layer dual-boot supplement patterns on top when a co-installed OS is configured.
|
||||||
|
if let (Some(ref mut primary), Some(ref dualboot)) = (&mut pf, &migration.dual_boot_with) {
|
||||||
|
if let Ok(supplement) = patterns::load_supplement(dualboot) {
|
||||||
|
primary.extend(supplement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pf
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,42 @@ pub fn load(
|
||||||
anyhow::bail!("pattern file not found for {source_os}-to-{distro_family}.toml")
|
anyhow::bail!("pattern file not found for {source_os}-to-{distro_family}.toml")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PatternFile {
|
||||||
|
/// Merge patterns and log_paths from `other` into this file.
|
||||||
|
/// Used to layer a dual-boot supplement on top of the primary pattern file.
|
||||||
|
pub fn extend(&mut self, other: PatternFile) {
|
||||||
|
self.patterns.extend(other.patterns);
|
||||||
|
for (k, v) in other.log_paths {
|
||||||
|
self.log_paths.entry(k).or_insert(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load a supplementary pattern file by name (e.g. "windows" loads `dualboot-windows.toml`).
|
||||||
|
/// Supplement files cover coexistence-specific issues and are merged into the primary file.
|
||||||
|
pub fn load_supplement(name: &str) -> Result<PatternFile> {
|
||||||
|
let filename = format!("dualboot-{name}.toml");
|
||||||
|
let candidates = [
|
||||||
|
format!("patterns/{filename}"),
|
||||||
|
format!("src-tauri/patterns/{filename}"),
|
||||||
|
format!("/usr/share/robin/patterns/{filename}"),
|
||||||
|
];
|
||||||
|
for path in &candidates {
|
||||||
|
let content = match std::fs::read_to_string(path) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
match toml::from_str::<PatternFile>(&content) {
|
||||||
|
Ok(pf) => return Ok(pf),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!("patterns: failed to parse supplement {path}: {e}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
anyhow::bail!("supplement not found: {filename}")
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn classify(event: &SystemEvent, pf: &PatternFile) -> Option<MatchedEvent> {
|
pub fn classify(event: &SystemEvent, pf: &PatternFile) -> Option<MatchedEvent> {
|
||||||
for pattern in &pf.patterns {
|
for pattern in &pf.patterns {
|
||||||
|
|
|
||||||
|
|
@ -2,60 +2,150 @@
|
||||||
<div class="onboarding">
|
<div class="onboarding">
|
||||||
<div class="onboarding-card">
|
<div class="onboarding-card">
|
||||||
<div class="robin-logo">🐦</div>
|
<div class="robin-logo">🐦</div>
|
||||||
<h1>Hi, I'm Robin.</h1>
|
|
||||||
<p>I help you find your feet on Linux. Before we start — what were you using before?</p>
|
|
||||||
|
|
||||||
<div class="os-choices">
|
<!-- Step 1: Source OS -->
|
||||||
<button
|
<template v-if="step === 1">
|
||||||
v-for="os in osOptions"
|
<h1>Hi, I'm Robin.</h1>
|
||||||
:key="os.value"
|
<p>I help you find your feet on Linux. Before we start — what were you using before?</p>
|
||||||
class="os-btn"
|
<div class="os-choices">
|
||||||
:class="{ selected: selectedOs === os.value }"
|
<button
|
||||||
@click="selectedOs = os.value"
|
v-for="os in osOptions"
|
||||||
>
|
:key="os.value"
|
||||||
{{ os.label }}
|
class="os-btn"
|
||||||
|
:class="{ selected: selectedOs === os.value }"
|
||||||
|
@click="selectedOs = os.value"
|
||||||
|
>
|
||||||
|
{{ os.label }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p class="hint">Robin uses this to explain things in terms you already know.</p>
|
||||||
|
<button class="continue-btn" :disabled="!selectedOs" @click="advanceFromStep1">
|
||||||
|
Continue
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</template>
|
||||||
|
|
||||||
<p class="hint">Robin uses this to explain things in terms you already know.</p>
|
<!-- Step 2: Source Linux distro (Linux-to-Linux only) -->
|
||||||
|
<template v-else-if="step === 2 && selectedOs === 'linux'">
|
||||||
|
<h1>Which distro were you on?</h1>
|
||||||
|
<p>Robin loads the right patterns for your background.</p>
|
||||||
|
<div class="os-choices">
|
||||||
|
<button
|
||||||
|
v-for="d in linuxDistroOptions"
|
||||||
|
:key="d.value"
|
||||||
|
class="os-btn"
|
||||||
|
:class="{ selected: selectedSourceDistro === d.value }"
|
||||||
|
@click="selectedSourceDistro = d.value"
|
||||||
|
>
|
||||||
|
{{ d.label }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button class="continue-btn" @click="advanceFromLinuxDistro">
|
||||||
|
{{ selectedSourceDistro ? 'Continue' : 'Skip' }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
<button
|
<!-- Step 3: Dual-boot question (Windows / macOS only) -->
|
||||||
class="continue-btn"
|
<template v-else-if="step === 3">
|
||||||
:disabled="!selectedOs"
|
<h1>Are you keeping {{ sourceOsLabel }} alongside Linux?</h1>
|
||||||
@click="submit"
|
<p>Dual-boot setups have some extra quirks Robin can watch for — like Windows locking shared drives.</p>
|
||||||
>
|
<div class="os-choices">
|
||||||
Let's go
|
<button
|
||||||
</button>
|
class="os-btn"
|
||||||
|
:class="{ selected: dualBoot === true }"
|
||||||
|
@click="dualBoot = true"
|
||||||
|
>
|
||||||
|
Yes, dual-booting
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="os-btn"
|
||||||
|
:class="{ selected: dualBoot === false }"
|
||||||
|
@click="dualBoot = false"
|
||||||
|
>
|
||||||
|
No, Linux only
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button class="continue-btn" :disabled="dualBoot === null" @click="submit">
|
||||||
|
Let's go
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Final step for OS types that skip step 3 -->
|
||||||
|
<template v-else-if="step === 'submit'">
|
||||||
|
<h1>All set.</h1>
|
||||||
|
<p>Robin is ready to help.</p>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { invoke } from '@tauri-apps/api/core'
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
|
|
||||||
const emit = defineEmits<{ complete: [] }>()
|
const emit = defineEmits<{ complete: [] }>()
|
||||||
|
|
||||||
|
type Step = 1 | 2 | 3 | 'submit'
|
||||||
|
|
||||||
|
const step = ref<Step>(1)
|
||||||
const selectedOs = ref('')
|
const selectedOs = ref('')
|
||||||
|
const selectedSourceDistro = ref('')
|
||||||
|
const dualBoot = ref<boolean | null>(null)
|
||||||
|
|
||||||
const osOptions = [
|
const osOptions = [
|
||||||
{ value: 'windows', label: 'Windows' },
|
{ value: 'windows', label: 'Windows' },
|
||||||
{ value: 'macos', label: 'macOS' },
|
{ value: 'macos', label: 'macOS' },
|
||||||
{ value: 'linux', label: 'Another Linux distro' },
|
{ value: 'linux', label: 'Another Linux distro' },
|
||||||
|
{ value: 'android', label: 'Android / ChromeOS' },
|
||||||
|
{ value: 'ipad', label: 'iPhone / iPad' },
|
||||||
]
|
]
|
||||||
|
|
||||||
async function submit() {
|
const linuxDistroOptions = [
|
||||||
|
{ value: 'ubuntu', label: 'Debian / Ubuntu / Mint / Pop!_OS' },
|
||||||
|
{ value: 'fedora', label: 'Fedora / RHEL / CentOS' },
|
||||||
|
{ value: 'arch', label: 'Arch / Manjaro / EndeavourOS' },
|
||||||
|
{ value: 'opensuse', label: 'openSUSE' },
|
||||||
|
]
|
||||||
|
|
||||||
|
const sourceOsLabel = computed(() =>
|
||||||
|
osOptions.find(o => o.value === selectedOs.value)?.label ?? selectedOs.value
|
||||||
|
)
|
||||||
|
|
||||||
|
function advanceFromStep1() {
|
||||||
if (!selectedOs.value) return
|
if (!selectedOs.value) return
|
||||||
|
if (selectedOs.value === 'linux') {
|
||||||
|
step.value = 2
|
||||||
|
} else if (selectedOs.value === 'windows' || selectedOs.value === 'macos') {
|
||||||
|
step.value = 3
|
||||||
|
} else {
|
||||||
|
// mobile users: no dual-boot question
|
||||||
|
submitOnboarding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function advanceFromLinuxDistro() {
|
||||||
|
// Linux-to-Linux users don't get the dual-boot question
|
||||||
|
submitOnboarding()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submit() {
|
||||||
|
submitOnboarding()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitOnboarding() {
|
||||||
const distro = await detectDistro()
|
const distro = await detectDistro()
|
||||||
|
const sourceDistro = selectedSourceDistro.value || undefined
|
||||||
|
const dualBootWith = dualBoot.value === true ? selectedOs.value : undefined
|
||||||
|
|
||||||
await invoke('complete_onboarding', {
|
await invoke('complete_onboarding', {
|
||||||
sourceOs: selectedOs.value,
|
sourceOs: selectedOs.value,
|
||||||
distro,
|
distro,
|
||||||
|
sourceDistro,
|
||||||
|
dualBootWith,
|
||||||
})
|
})
|
||||||
emit('complete')
|
emit('complete')
|
||||||
}
|
}
|
||||||
|
|
||||||
async function detectDistro(): Promise<string> {
|
async function detectDistro(): Promise<string> {
|
||||||
// M1: read /etc/os-release via tauri-plugin-fs
|
|
||||||
// M0: return placeholder
|
|
||||||
return 'unknown'
|
return 'unknown'
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -70,7 +160,7 @@ async function detectDistro(): Promise<string> {
|
||||||
}
|
}
|
||||||
|
|
||||||
.onboarding-card {
|
.onboarding-card {
|
||||||
max-width: 320px;
|
max-width: 340px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,6 +185,7 @@ p { color: #aaa; line-height: 1.5; margin-bottom: 20px; }
|
||||||
color: #e0e0e0;
|
color: #e0e0e0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: border-color 0.15s, background 0.15s;
|
transition: border-color 0.15s, background 0.15s;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.os-btn:hover { border-color: #6c8ebf; }
|
.os-btn:hover { border-color: #6c8ebf; }
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue