#!/usr/bin/env python3
"""LinuxDoors Kernel Config -- a PySide6 GUI for toggling optional kernel
features (WiFi/network drivers, filesystems, virtualization, security
hardening) and triggering a real, from-source kernel rebuild to apply them.

Design, worth keeping in mind for any future change:
- Reads the CURRENTLY RUNNING kernel's real .config
  (/boot/config-$(uname -r)) to show the genuine current state of every
  toggle -- never guessed, never cached.
- "Apply" downloads the real kernel source the same way the package
  manager's own "Build from source" path already does (POST
  /api/build-token, then GET /api/build-source over plain HTTPS -- no new
  protocol), sandbox-compiles it as the unprivileged linuxdoors-build
  user (same sandboxing linuxdoors_pkg_backend.install_from_source
  already uses), with EXTRA_KERNEL_CONFIG set to the user's chosen
  scripts/config lines -- packages/linux/build.sh's own real extension
  point for this exact purpose, so the baseline config this app builds
  from is always identical to what the project's own build.sh produces.
- The new kernel ALWAYS gets a distinct LOCALVERSION ("-custom" by
  default, "-customN" if that already exists) so it lands as a brand new
  /boot/vmlinuz-<release>-linuxdoors + /lib/modules/<release>/ alongside
  the original -- the currently-running kernel is NEVER overwritten, and
  a new GRUB menu entry is simply APPENDED (the existing default entry
  and its own index are left untouched). This is the single most
  important safety property of this app: a bad combination of toggles in
  a custom build can never brick the original, already-working boot.
- Deliberately scoped to the INSTALLED system only (this app refuses to
  run from a live ISO session -- a kernel rebuild there can't persist
  across reboot anyway, and the live grub.cfg has a completely different,
  hand-rolled-initramfs boot path this app doesn't attempt to replicate).
"""
import os
import re
import sys
import glob
import json
import time
import shutil
import tempfile
import subprocess
import urllib.request

from PySide6.QtCore import Qt, QThread, Signal
from PySide6.QtGui import QPainter, QColor, QFont, QPen
from PySide6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QTabWidget, QScrollArea, QCheckBox, QLabel, QPushButton, QFrame,
    QMessageBox, QDialog, QTextEdit, QProgressBar, QInputDialog,
    QLineEdit,
)

# This system has never had a system-wide CA trust store set up -- Python's
# ssl module needs an explicit bundle or HTTPS verification fails with
# CERTIFICATE_VERIFY_FAILED. Same fix, same real path, as every other
# linuxdoors.com HTTPS caller in this project (linuxdoors_pkg_backend.py,
# linuxdoors-pkg-watch).
os.environ.setdefault(
    "SSL_CERT_FILE",
    "/usr/lib/python3.14/site-packages/pip/_vendor/certifi/cacert.pem",
)

API_BASE = "https://linuxdoors.com"
CONFIG_PATH_TMPL = "/boot/config-{release}"
GRUB_CFG = "/boot/grub/grub.cfg"
BUILD_USER = "linuxdoors-build"

# ---------------------------------------------------------------------------
# Curated toggle catalog. Each entry: (display name, description, Kconfig
# symbol, kind) where kind is "module" (scripts/config --module X) or
# "enable" (scripts/config --enable X, for things that must be built in).
# Deliberately NOT a full menuconfig clone -- real, commonly-requested
# hardware/feature toggles only, each individually verified to be a real,
# plain in-tree Kconfig symbol (no DKMS/out-of-tree modules like ZFS or
# VirtualBox's own vboxdrv -- those need their own separate source tree
# entirely, not a kernel config toggle, and are out of scope here).
# ---------------------------------------------------------------------------
CATEGORIES = [
    ("Network Drivers", [
        ("Intel WiFi (iwlwifi)", "Intel AC/AX WiFi chipsets (most modern Intel laptops).", "IWLWIFI", "module"),
        ("Realtek WiFi (rtw88)", "Realtek 8822/8821/8723 series USB/PCIe WiFi.", "RTW88", "module"),
        ("Realtek USB WiFi (rtl8xxxu)", "Older Realtek USB WiFi dongles (8188/8192/8723 series).", "RTL8XXXU", "module"),
        ("Atheros WiFi (ath9k)", "Atheros AR9xxx PCIe/USB WiFi chipsets.", "ATH9K", "module"),
        ("Atheros WiFi (ath10k)", "Atheros/Qualcomm QCA98xx 802.11ac WiFi chipsets.", "ATH10K", "module"),
        ("Broadcom WiFi (brcmfmac)", "Broadcom FullMAC WiFi (common in many laptops, Raspberry Pi).", "BRCMFMAC", "module"),
        ("Bluetooth (USB)", "Generic USB Bluetooth adapter support.", "BT_HCIBTUSB", "module"),
        ("USB Ethernet (Realtek r8152)", "USB-to-Ethernet adapters using the Realtek RTL8152/8153 chipset.", "USB_RTL8152", "module"),
    ]),
    ("Filesystems", [
        ("NTFS (read/write)", "Modern in-kernel NTFS3 driver -- read and write Windows NTFS volumes.", "NTFS3_FS", "module"),
        ("exFAT", "Read/write exFAT volumes (common on large USB drives, SD cards).", "EXFAT_FS", "module"),
        ("Btrfs", "Copy-on-write filesystem with snapshots and built-in RAID.", "BTRFS_FS", "module"),
        ("XFS", "High-performance filesystem, common on servers.", "XFS_FS", "module"),
        ("F2FS", "Flash-Friendly File System, optimized for SSDs/eMMC/SD cards.", "F2FS_FS", "module"),
    ]),
    ("Virtualization", [
        ("KVM (Intel VT-x)", "Run virtual machines on Intel CPUs via QEMU/libvirt.", "KVM_INTEL", "module"),
        ("KVM (AMD-V)", "Run virtual machines on AMD CPUs via QEMU/libvirt.", "KVM_AMD", "module"),
        ("vhost-net", "Accelerated guest networking for KVM virtual machines.", "VHOST_NET", "module"),
    ]),
    ("Security / Hardening", [
        ("Hardened usercopy checks", "Extra bounds-checking on copy_to/from_user() -- built in, no runtime cost in practice.", "HARDENED_USERCOPY", "enable"),
        ("Strong stack protector", "Compiler-level stack-smashing protection on more functions than the default.", "STACKPROTECTOR_STRONG", "enable"),
        ("Kernel address randomization (KASLR)", "Randomizes where the kernel itself is loaded in memory, raising the bar for exploits.", "RANDOMIZE_BASE", "enable"),
        ("SELinux (kernel support only)", "Kernel-side Mandatory Access Control hooks. Note: this enables kernel support only -- LinuxDoors has no policy/userspace tooling (semanage, restorecon, ...) yet, so this alone does not give you a working, enforcing SELinux system.", "SECURITY_SELINUX", "enable"),
    ]),
]


def real_kernel_release():
    return subprocess.check_output(["uname", "-r"], text=True).strip()


def read_config_states(release):
    """Parse /boot/config-<release> into {SYMBOL: 'y'|'m'|None}."""
    path = CONFIG_PATH_TMPL.format(release=release)
    states = {}
    if not os.path.isfile(path):
        return states
    pat_set = re.compile(r"^CONFIG_([A-Za-z0-9_]+)=([ym])\s*$")
    pat_unset = re.compile(r"^# CONFIG_([A-Za-z0-9_]+) is not set\s*$")
    with open(path, encoding="utf-8", errors="replace") as f:
        for line in f:
            m = pat_set.match(line)
            if m:
                states[m.group(1)] = m.group(2)
                continue
            m = pat_unset.match(line)
            if m:
                states[m.group(1)] = None
    return states


def status_text(states, symbol):
    v = states.get(symbol)
    if v == "y":
        return "Built-in"
    if v == "m":
        return "Module (loaded)"
    return "Not built"


def is_live_session():
    """Refuse to run on a live-ISO session -- rebuilding there can't
    persist across reboot, and the live boot path is a completely
    different hand-rolled-initramfs setup this app never touches."""
    try:
        with open("/proc/cmdline", encoding="utf-8") as f:
            cmdline = f.read()
        return "LiveOS" in cmdline or os.path.ismount("/run/initramfs/live")
    except Exception:
        return False


# ---------------------------------------------------------------------------
# Door-mark header, matching every other native LinuxDoors app's visual
# identity -- same construction as the GRUB splash / Phase 1.5 installer /
# Package Manager, just drawn with QPainter instead of Cairo/GTK.
# ---------------------------------------------------------------------------
class DoorMark(QFrame):
    def __init__(self, w=120, h=148):
        super().__init__()
        self.setFixedSize(w, h)

    def paintEvent(self, _event):
        p = QPainter(self)
        p.setRenderHint(QPainter.Antialiasing)
        w, h = self.width(), self.height()
        frame_rect = (int(w * 0.12), int(h * 0.08), int(w * 0.76), int(h * 0.86))
        p.setPen(QPen(QColor("#E0B44A"), 3))
        p.setBrush(QColor("#fbeed2"))
        p.drawRect(*frame_rect)
        opening_margin = 8
        p.setBrush(QColor("#241246"))
        p.setPen(Qt.NoPen)
        p.drawRect(frame_rect[0] + opening_margin, frame_rect[1] + opening_margin,
                   frame_rect[2] - opening_margin * 2, frame_rect[3] - opening_margin * 2)
        # ajar violet leaf
        leaf_x0 = frame_rect[0] + opening_margin + 4
        leaf_y0 = frame_rect[1] + opening_margin + 4
        leaf_w = int((frame_rect[2] - opening_margin * 2) * 0.62)
        leaf_h = frame_rect[3] - opening_margin * 2 - 8
        p.setPen(QPen(QColor("#E0B44A"), 2))
        p.setBrush(QColor("#5b359e"))
        p.drawRect(leaf_x0, leaf_y0, leaf_w, leaf_h)
        knob_x = leaf_x0 + leaf_w - 10
        knob_y = leaf_y0 + leaf_h // 2
        p.setBrush(QColor("#E0B44A"))
        p.setPen(Qt.NoPen)
        p.drawEllipse(knob_x, knob_y, 6, 6)
        p.end()


# ---------------------------------------------------------------------------
# Backend: sandboxed download + compile + install, mirroring
# linuxdoors_pkg_backend.install_from_source's own real mechanics.
# ---------------------------------------------------------------------------
class BuildWorker(QThread):
    progress = Signal(str)
    finished_ok = Signal(bool, str)

    def __init__(self, extra_config_lines, root_password):
        super().__init__()
        self.extra_config_lines = extra_config_lines
        self.root_password = root_password

    def _run_root(self, cmd, **kw):
        """Run a command as root via sudo -S, feeding the password on
        stdin -- this app is itself launched already-elevated (see the
        launcher script), so in practice this just runs directly; kept
        as a real su fallback for a non-elevated launch too."""
        if os.geteuid() == 0:
            return subprocess.run(cmd, check=True, **kw)
        p = subprocess.run(
            ["su", "-c", " ".join(cmd), "root"],
            input=self.root_password + "\n", text=True, check=True, **kw)
        return p

    def run(self):
        try:
            self._do_build()
            self.finished_ok.emit(True, "")
        except Exception as exc:  # noqa: BLE001 -- surfaced straight to the UI
            self.finished_ok.emit(False, str(exc))

    def _do_build(self):
        emit = self.progress.emit
        emit("Requesting a real build token from linuxdoors.com...")
        pkg_info = self._api_json("/api/packages")
        linux_row = next((r for r in pkg_info["packages"] if r["name"] == "linux"), None)
        if not linux_row:
            raise RuntimeError("linux package not found in the catalog")
        token_resp = self._api_json(
            "/api/build-token", method="POST",
            data={"package": "linux", "version": linux_row["version"]})
        source_url = API_BASE + token_resp["source_url"]

        work_dir = tempfile.mkdtemp(prefix="ldkc-")
        os.chmod(work_dir, 0o755)
        tar_path = os.path.join(work_dir, "linux-src.tar")
        emit(f"Downloading kernel source ({linux_row['version']})... this is large, be patient.")
        urllib.request.urlretrieve(source_url, tar_path)

        emit("Extracting...")
        subprocess.run(["tar", "-xf", tar_path, "-C", work_dir], check=True)
        pkg_dir = os.path.join(work_dir, "linux")
        if not os.path.isdir(pkg_dir):
            # tar layout is <name>/{build.sh,metadata.json,sources/*}
            candidates = [d for d in glob.glob(os.path.join(work_dir, "*")) if os.path.isdir(d)]
            pkg_dir = candidates[0] if candidates else work_dir

        emit("Sandboxing build directory to the linuxdoors-build user...")
        self._run_root(["chown", "-R", f"{BUILD_USER}:{BUILD_USER}", work_dir])

        src_dir = os.path.join(pkg_dir, "sources")
        destdir = os.path.join(work_dir, "destdir")
        os.makedirs(destdir, exist_ok=True)
        self._run_root(["chown", "-R", f"{BUILD_USER}:{BUILD_USER}", destdir])

        extra_cfg = "\n".join(self.extra_config_lines)
        # Always pin a distinct LOCALVERSION so this build can never
        # collide with / overwrite the currently-running kernel's own
        # /boot and /lib/modules files -- see module docstring. No
        # surrounding quotes needed: the suffix is always one word
        # (-custom, -custom1, ...), and build.sh's own extension-point
        # loop word-splits each line unquoted before calling
        # scripts/config, so a quoted value here would actually pass a
        # literal quote character through as part of the string.
        suffix = self._pick_localversion_suffix()
        extra_cfg = f"--set-str LOCALVERSION {suffix}\n" + extra_cfg

        # Pass the (possibly multi-line) config text to the sandboxed
        # build via a real FILE, not interpolated into a shell command
        # string -- embedding a multi-line value with its own quoting
        # needs directly into an f-string-built shell command is exactly
        # the class of escaping bug this project has hit before (GN's
        # own rustc_version shell-quoting break): a naive json.dumps()
        # embed here would turn real newlines into a literal two-char
        # "\n" that bash's own double-quote rules do NOT re-expand, and
        # any embedded double-quote could terminate the outer command's
        # own quoting early. A small wrapper SCRIPT that reads the value
        # back via $(cat ...) inside a real bash context sidesteps all of
        # that, and is the same technique this project's own build
        # scripts already use for multi-line heredoc content elsewhere.
        cfg_file = os.path.join(work_dir, "extra_config.txt")
        with open(cfg_file, "w", encoding="utf-8") as f:
            f.write(extra_cfg)
        wrapper_path = os.path.join(work_dir, "run_build.sh")
        with open(wrapper_path, "w", encoding="utf-8") as f:
            f.write(
                "#!/bin/bash\n"
                "set -e\n"
                f'export EXTRA_KERNEL_CONFIG="$(cat {cfg_file})"\n'
                f"exec env -i HOME=/home/{BUILD_USER} PATH=/usr/bin:/usr/sbin "
                f"LFS=/ LFS_TGT=x86_64-lfs-linux-gnu SRC_DIR={src_dir} "
                f"PKG_DESTDIR={destdir} MAKEFLAGS=-j$(nproc) "
                f'EXTRA_KERNEL_CONFIG="$EXTRA_KERNEL_CONFIG" '
                f"bash -e {pkg_dir}/build.sh\n"
            )
        os.chmod(wrapper_path, 0o755)
        self._run_root(["chown", f"{BUILD_USER}:{BUILD_USER}", cfg_file, wrapper_path])

        emit("Compiling kernel (this genuinely takes a while -- minutes to an hour "
             "depending on your hardware)...")
        proc = subprocess.Popen(
            ["su", BUILD_USER, "-s", "/bin/bash", "-c", wrapper_path],
            stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1)
        tail = []
        for line in proc.stdout:
            tail.append(line.rstrip("\n"))
            tail = tail[-200:]
            if len(tail) % 25 == 0:
                emit(tail[-1])
        rc = proc.wait()
        if rc != 0:
            raise RuntimeError("kernel build failed:\n" + "\n".join(tail[-40:]))

        emit("Installing the new kernel (keeping your current one intact)...")
        self._install(destdir)
        emit("Done.")

    def _pick_localversion_suffix(self):
        n = 0
        suffix = "-custom"
        while glob.glob(f"/boot/vmlinuz-*{suffix}"):
            n += 1
            suffix = f"-custom{n}"
        return suffix

    def _install(self, destdir):
        boots = glob.glob(os.path.join(destdir, "boot", "vmlinuz-*"))
        if not boots:
            raise RuntimeError("build finished but produced no kernel image")
        vmlinuz = boots[0]
        kernel_name = os.path.basename(vmlinuz)
        release = kernel_name.replace("vmlinuz-", "").replace("-linuxdoors", "")

        self._run_root(["cp", "-v", vmlinuz, f"/boot/{kernel_name}"])
        sysmap = os.path.join(destdir, "boot", f"System.map-{release}")
        if os.path.isfile(sysmap):
            self._run_root(["cp", "-v", sysmap, f"/boot/System.map-{release}"])
        cfg = os.path.join(destdir, "boot", f"config-{release}")
        if os.path.isfile(cfg):
            self._run_root(["cp", "-v", cfg, f"/boot/config-{release}"])
        modsrc = os.path.join(destdir, "lib", "modules", release)
        if os.path.isdir(modsrc):
            self._run_root(["rsync", "-a", modsrc + "/", f"/lib/modules/{release}/"])

        self._append_grub_entry(kernel_name, release)

    def _append_grub_entry(self, kernel_name, release):
        if not os.path.isfile(GRUB_CFG):
            raise RuntimeError(f"{GRUB_CFG} not found -- can't add a boot entry")
        with open(GRUB_CFG, encoding="utf-8") as f:
            cfg_text = f.read()
        m = re.search(r'linux\s+/boot/\S+\s+(root=\S+)', cfg_text)
        root_arg = m.group(1) if m else "root=/dev/sda1"
        entry = (
            f'\nmenuentry "LinuxDoors (Custom Kernel {release})" {{\n'
            f"    linux /boot/{kernel_name} {root_arg} rw "
            "console=tty0 init=/usr/lib/systemd/systemd\n"
            "}\n"
        )
        new_text = cfg_text + entry
        tmp_path = "/tmp/.ldkc-grub.cfg"
        with open(tmp_path, "w", encoding="utf-8") as f:
            f.write(new_text)
        self._run_root(["cp", tmp_path, GRUB_CFG])
        os.remove(tmp_path)

    def _api_json(self, path, method="GET", data=None):
        req = urllib.request.Request(API_BASE + path, method=method)
        body = None
        if data is not None:
            body = json.dumps(data).encode()
            req.add_header("Content-Type", "application/json")
        with urllib.request.urlopen(req, data=body, timeout=30) as resp:
            return json.loads(resp.read().decode())


class ProgressDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("LinuxDoors Kernel Config")
        self.setMinimumSize(560, 360)
        lay = QVBoxLayout(self)
        self.label = QLabel("Starting...")
        self.label.setWordWrap(True)
        lay.addWidget(self.label)
        self.bar = QProgressBar()
        self.bar.setRange(0, 0)
        lay.addWidget(self.bar)
        self.log = QTextEdit()
        self.log.setReadOnly(True)
        lay.addWidget(self.log)
        self.close_btn = QPushButton("Close")
        self.close_btn.clicked.connect(self.accept)
        self.close_btn.setEnabled(False)
        lay.addWidget(self.close_btn)

    def on_progress(self, text):
        self.label.setText(text)
        self.log.append(text)

    def on_finished(self, ok, error):
        self.bar.setRange(0, 1)
        self.bar.setValue(1)
        self.close_btn.setEnabled(True)
        if ok:
            self.label.setText("Done -- a new GRUB entry was added for your custom kernel.")
            QMessageBox.information(
                self, "Kernel build complete",
                "Your custom kernel was built and installed successfully.\n\n"
                "Your ORIGINAL kernel is untouched and still the default boot "
                "entry. To try the new one, reboot and pick \"LinuxDoors "
                "(Custom Kernel ...)\" from the GRUB menu.")
        else:
            self.label.setText("Build failed.")
            QMessageBox.critical(self, "Kernel build failed", error[-4000:])


class CategoryTab(QScrollArea):
    def __init__(self, items, states):
        super().__init__()
        self.setWidgetResizable(True)
        inner = QWidget()
        lay = QVBoxLayout(inner)
        self.checkboxes = []
        for name, desc, symbol, kind in items:
            row = QFrame()
            row.setObjectName("toggleRow")
            row_lay = QHBoxLayout(row)
            cb = QCheckBox(name)
            cur = status_text(states, symbol)
            cb.setChecked(cur != "Not built")
            cb.setProperty("symbol", symbol)
            cb.setProperty("kind", kind)
            cb.setProperty("was_on", cur != "Not built")
            row_lay.addWidget(cb, 0)
            info = QLabel(f"{desc}\nCurrent status: {cur}")
            info.setWordWrap(True)
            info.setObjectName("toggleDesc")
            row_lay.addWidget(info, 1)
            lay.addWidget(row)
            self.checkboxes.append(cb)
        lay.addStretch(1)
        self.setWidget(inner)

    def changed_on(self):
        """Symbols the user newly turned ON that weren't already built/loaded."""
        out = []
        for cb in self.checkboxes:
            if cb.isChecked() and not cb.property("was_on"):
                out.append((cb.property("symbol"), cb.property("kind")))
        return out


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("LinuxDoors Kernel Config")
        self.resize(820, 620)
        self.release = real_kernel_release()
        self.states = read_config_states(self.release)

        central = QWidget()
        self.setCentralWidget(central)
        outer = QVBoxLayout(central)

        header = QHBoxLayout()
        header.addWidget(DoorMark())
        title_box = QVBoxLayout()
        title = QLabel("LinuxDoors Kernel Config")
        title.setObjectName("appTitle")
        subtitle = QLabel(f"Running kernel: {self.release}")
        subtitle.setObjectName("appSubtitle")
        title_box.addWidget(title)
        title_box.addWidget(subtitle)
        header.addLayout(title_box)
        header.addStretch(1)
        outer.addLayout(header)

        self.tabs = QTabWidget()
        self.tab_widgets = []
        for cat_name, items in CATEGORIES:
            tab = CategoryTab(items, self.states)
            self.tabs.addTab(tab, cat_name)
            self.tab_widgets.append(tab)
        outer.addWidget(self.tabs, 1)

        btn_row = QHBoxLayout()
        btn_row.addStretch(1)
        self.apply_btn = QPushButton("Apply & Rebuild Kernel")
        self.apply_btn.setObjectName("applyBtn")
        self.apply_btn.clicked.connect(self.on_apply)
        btn_row.addWidget(self.apply_btn)
        outer.addLayout(btn_row)

        self.setStyleSheet(STYLE)

    def on_apply(self):
        changed = []
        for tab in self.tab_widgets:
            changed.extend(tab.changed_on())
        if not changed:
            QMessageBox.information(self, "Nothing to apply",
                                     "No new features were selected.")
            return

        names = "\n".join(f"  - {sym}" for sym, _ in changed)
        resp = QMessageBox.question(
            self, "Rebuild the kernel?",
            "This will download the real kernel source, compile it with these "
            f"additional features enabled, and install it as a NEW kernel "
            f"(your current one is never touched):\n\n{names}\n\n"
            "This downloads a large source tree and can take anywhere from a "
            "few minutes to over an hour to compile, depending on your "
            "hardware. Continue?",
            QMessageBox.Yes | QMessageBox.No)
        if resp != QMessageBox.Yes:
            return

        root_password = ""
        if os.geteuid() != 0:
            pw, ok = QInputDialog.getText(
                self, "Root password required",
                "Installing the new kernel needs root:",
                QLineEdit.Password)
            if not ok:
                return
            root_password = pw

        lines = []
        for sym, kind in changed:
            flag = "--module" if kind == "module" else "--enable"
            lines.append(f"{flag} {sym}")

        dlg = ProgressDialog(self)
        self.worker = BuildWorker(lines, root_password)
        self.worker.progress.connect(dlg.on_progress)
        self.worker.finished_ok.connect(dlg.on_finished)
        self.worker.start()
        dlg.exec()


STYLE = """
QMainWindow, QWidget { background-color: #1c1038; color: #fbeed2; }
#appTitle { font-size: 20px; font-weight: bold; color: #fbeed2; }
#appSubtitle { color: #c8b8e8; }
QTabWidget::pane { border: 1px solid #5b359e; background: #241246; }
QTabBar::tab { background: #241246; color: #c8b8e8; padding: 8px 16px; }
QTabBar::tab:selected { background: #5b359e; color: #fbeed2; }
#toggleRow { background: #241246; border: 1px solid #3a2060; border-radius: 6px; margin: 4px; padding: 6px; }
#toggleDesc { color: #c8b8e8; font-size: 11px; }
QCheckBox { color: #fbeed2; font-weight: bold; min-width: 220px; }
#applyBtn { background-color: #E0B44A; color: #1c1038; font-weight: bold;
            padding: 10px 20px; border-radius: 6px; }
#applyBtn:hover { background-color: #f0c860; }
QScrollArea { border: none; }
QMessageBox, QDialog { background-color: #1c1038; color: #fbeed2; }
QTextEdit { background-color: #0f0820; color: #c8e0c8; font-family: monospace; }
QPushButton { background-color: #5b359e; color: #fbeed2; padding: 6px 14px; border-radius: 4px; }
QPushButton:hover { background-color: #6f44b8; }
QLineEdit { background-color: #fbeed2; color: #1c1038; padding: 4px; }
"""


def main():
    if os.geteuid() != 0:
        print("LinuxDoors Kernel Config must run as root -- re-launch via its "
              "Desktop icon or `su -c linuxdoors-kernel-config`.", file=sys.stderr)
        sys.exit(1)
    if is_live_session():
        app = QApplication(sys.argv)
        QMessageBox.critical(
            None, "Not available in live mode",
            "LinuxDoors Kernel Config only works on an installed system -- "
            "a kernel rebuild in a live/USB session can't persist across "
            "reboot. Install LinuxDoors to a real disk first.")
        sys.exit(1)

    app = QApplication(sys.argv)
    win = MainWindow()
    win.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()
