Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c6ce91f
Please enter the commit message for your changes. Lines starting
May 5, 2026
2d9cae9
Veuillez saisir le message de validation pour vos modifications. Les…
Radiump123 May 5, 2026
14896eb
DO NOT PUSH TO HOG185'S LUFUS!
Radiump123 May 5, 2026
2227a62
Veuillez saisir le message de validation pour vos modifications. Les…
Radiump123 May 6, 2026
5af1dce
Update tweaks.py
Radiump123 May 6, 2026
ca85a98
Merge branch 'feature-win-tweaks' of https://github.com/Radiump123/Lu…
May 6, 2026
732856d
Tests must be fixed. Pwease.
May 7, 2026
205d3b3
FIXED! Tests works, and LSBLK is replaced w/Python native tooling1
May 7, 2026
f77fd1c
Issue #178 fixed.
May 7, 2026
dc34beb
Please enter the commit message for your changes. Lines starting
May 7, 2026
62f15d5
Please enter the commit message for your changes. Lines starting
May 7, 2026
5d53b8f
Revert " Please enter the commit message for your changes. Lines star…
May 7, 2026
79817e8
Revert "Issue 178 fixed."
May 7, 2026
cb60160
Please enter the commit message for your changes. Lines starting
May 7, 2026
a8a066f
Please enter the commit message for your changes. Lines starting
May 7, 2026
f6fc2a3
Please enter the commit message for your changes. Lines starting
May 7, 2026
dbc3e64
Please enter the commit message for your changes. Lines starting
May 7, 2026
d4f7118
Proceeding to fix goofy encoding issues
May 7, 2026
09fe322
Please enter the commit message for your changes. Lines starting
May 7, 2026
f30d249
Veuillez saisir le message de validation pour vos modifications. Les…
Radiump123 May 7, 2026
7505cc9
Veuillez saisir le message de validation pour vos modifications. Les…
Radiump123 May 7, 2026
8791cf1
Fixed detection, lets see if shit works now...
Radiump123 May 7, 2026
41eff88
Veuillez saisir le message de validation pour vos modifications. Les…
Radiump123 May 7, 2026
f227f74
Add pyudev to Python Compatibility workflow
Radiump123 May 7, 2026
2ece545
Fix test FakeStderr to use read1() for progress parsing
Radiump123 May 7, 2026
15068d3
Veuillez saisir le message de validation pour vos modifications. Les…
Radiump123 May 7, 2026
342683c
Veuillez saisir le message de validation pour vos modifications. Les…
Radiump123 May 7, 2026
d39ecee
Veuillez saisir le message de validation pour vos modifications. Les…
Radiump123 May 7, 2026
9f94364
Ruff format
Radiump123 May 7, 2026
97b6a4d
Merge pull request #3 from Radiump123/less-deps-feature-win-tweaks
Radiump123 May 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install psutil pytest
pip install psutil pytest pyudev

- name: Install package (no-deps to avoid GUI reqs)
run: pip install -e . --no-deps
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/python-compatibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install psutil pytest packaging platformdirs
pip install psutil pytest packaging platformdirs pyudev

- name: Install package (no-deps to avoid GUI reqs)
run: pip install -e . --no-deps
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

## Beta Release Disclaimer

**Lufus** is currently in **Beta**.
**Lufus** is currently in **Beta**!

Lufus is a physical drive imaging and formatting utility written in Python, inspired by **Rufus** on Windows, with the goal of delivering a greater experience for Linux users.
While core functionality has been implemented, the project is still under active development. Users should expect bugs, incomplete features, and ongoing structural changes.
Expand Down
83 changes: 83 additions & 0 deletions deps.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Lufus External Dependencies (Non-Python)

## System Privileges
- pkexec (polkit)
- sudo
- runuser (util-linux)

## Flashing & Imaging
- dd (coreutils)
- sync (coreutils)
- cp (coreutils)
- rm (coreutils)
- mkdir (coreutils)

## Partitioning & Disk Info
- lsblk (util-linux)
- parted
- sfdisk (util-linux)
- partprobe (parted)
- udevadm (systemd/udev)
- wipefs (util-linux)
- blockdev (util-linux)
- badblocks (e2fsprogs)

## Filesystem Tools
- mkfs.vfat, fatlabel (dosfstools)
- mkfs.exfat (exfatprogs or exfat-utils)
- mkfs.ntfs / mkntfs, ntfslabel (ntfs-3g)
- mkfs.ext4, e2label (e2fsprogs)
- mkudffs, udflabel (udftools)

## Mounting & ISO Handling
- mount (util-linux)
- umount (util-linux)
- 7z (p7zip-full) - Used for Windows ISO detection
- isoinfo (genisoimage) - Fallback for Windows ISO detection

## Process & System Management
- lsof
- fuser (psmisc)
- pgrep (procps)
- which (debianutils or similar)
- stty (coreutils)
- xdg-user-dir (xdg-user-dirs)
- xdg-open (xdg-utils)

## Windows Image Operations (WIM)
- wimlib-imagex (wimlib / wimtools)
- wimmountrw (wimlib)
- wimunmount (wimlib)

## Bootloader Installation
- grub-install (grub2 / grub-common)

## GUI & System Libraries (PyQt6 Requirements)
- libgl1
- libx11-6
- libxcb1
- libxrender1
- fontconfig
- libfreetype6
- libxext6
- libxrandr2
- libxcursor1
- libxi6
- libxfixes3
- libxcomposite1
- libxdamage1

## Package Managers (Used for auto-installing missing tools)
- apt-get (Debian/Ubuntu)
- dnf (Fedora/RHEL)
- pacman (Arch)
- zypper (openSUSE)

## Python Environment
- python3
- python3-pip
- python3-venv

## Other (from requirements-system.txt)
- wget
- file
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "lufus"
version = "1.0.0" # keep synced with [tool.briefcase] version
version = "1.0.1b1" # keep synced with [tool.briefcase] version
description = "A rufus clone written in Python and designed to work with Linux."
readme = "README.md"
license = "MIT"
Expand All @@ -29,7 +29,7 @@ lufus = "lufus.__main__:main"
[tool.briefcase]
project_name = "Lufus"
bundle = "com.github.hog185"
version = "1.0.0" # keep synced with [project] version
version = "1.0.1b1" # keep synced with [project] version
url = "https://github.com/Hog185/Lufus"
license.file = "LICENSE"
author = "Hog185"
Expand All @@ -50,6 +50,7 @@ requires = [
"psutil>=7.2",
"pyqt6>=6.8.0",
"pyudev>=0.24.4",
"requests",
"packaging",
"platformdirs>=4.2"
]
Expand Down
20 changes: 9 additions & 11 deletions scripts/build-appimage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@ set -euo pipefail
ls -la

# Install system dependencies
## COMMENTED BECAUSE ALREADY HANDELED IN YAML
# echo "------------ Installing system libraries ------------"
# apt-get update && apt-get upgrade -y
# INSTALLER="apt-get install -y"
# if [[ -f requirements-system.txt ]]; then
# $INSTALLER $(cat requirements-system.txt) >> appimage-setup.log
# echo "System libraries installed."
# else
# echo "requirements-system.txt not found!"
# exit 1
# fi
echo "------------ Installing system libraries ------------"
INSTALLER="apt-get install -y"
if [[ -f requirements-system.txt ]]; then
$INSTALLER $(cat requirements-system.txt) >> appimage-setup.log
echo "System libraries installed."
else
echo "requirements-system.txt not found!"
exit 1
fi

if command -v python3 &>/dev/null; then
PYTHON=python3
Expand Down
38 changes: 19 additions & 19 deletions src/lufus/drives/find_usb.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pyudev
import psutil
import os
import subprocess
import getpass
from lufus import state
from lufus.lufus_logging import get_logger
Expand Down Expand Up @@ -42,30 +42,30 @@ def find_usb() -> dict[str, str]:
all_directories = _media_directories()
dir_set = set(all_directories)

context = pyudev.Context()

# Check each partition to see if it matches our potential mount points
for part in psutil.disk_partitions(all=True):
if part.mountpoint not in dir_set:
continue
mount_path = part.mountpoint
device_node = part.device
if not device_node:
continue

label = None
try:
label = subprocess.check_output(
["lsblk", "-d", "-n", "-o", "LABEL", device_node],
text=True,
timeout=5,
).strip()
except (subprocess.CalledProcessError, subprocess.TimeoutExpired):
pass

if not label:
label = os.path.basename(mount_path)

usbdict[mount_path] = label
log.info("Found USB: %s -> %s", mount_path, label)
if device_node:
label = os.path.basename(mount_path) # fallback
try:
st = os.stat(device_node)
udev_device = pyudev.Devices.from_device_number(context, "block", st.st_rdev)
udev_label = udev_device.get("ID_FS_LABEL")
if udev_label:
label = udev_label
except Exception:
log.debug(
"Failed to get label for %s via udev; using mountpoint basename",
device_node,
exc_info=True,
)
usbdict[mount_path] = label
log.info("Found USB: %s -> %s", mount_path, label)

return usbdict

Expand Down
122 changes: 38 additions & 84 deletions src/lufus/drives/formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,26 @@ def _find_tool(name: str) -> str:
return name


def _get_logical_block_size(drive: str) -> int:
"""Return the logical sector size of *drive* by reading sysfs.

Uses ``/sys/class/block/<dev>/queue/logical_block_size`` so no external
tool is required. Falls back to 512 bytes if the value cannot be read.
"""
dev_name = os.path.basename(drive)
sysfs_path = f"/sys/class/block/{dev_name}/queue/logical_block_size"
try:
with open(sysfs_path) as fh:
return int(fh.read().strip())
except Exception as exc:
log.warning(
"Could not read sector size from %s: %s. Using 512-byte default.",
sysfs_path,
exc,
)
return 512


#######


Expand Down Expand Up @@ -82,19 +102,23 @@ def unmount(drive: str = None) -> bool:

# mountain
def remount(drive: str = None) -> bool:
mount = None
if not drive:
mount, drive, _ = _get_mount_and_drive()
else:
# drive was supplied by caller; resolve mount point from current state
_, _, mount_dict = _get_mount_and_drive()
# find the mount point whose device node matches the given drive
mount = next((mp for mp, _label in mount_dict.items()), None)
# Look up mount point for the specified drive
import psutil

mount = None
for part in psutil.disk_partitions(all=True):
if part.device == drive:
mount = part.mountpoint
break

if not drive:
log.error("No drive node found. Cannot unmount.")
return False
if not mount:
log.error("No drive node or mount point found. Cannot remount.")
log.error("No mount point found for drive %s. Cannot remount.", drive)
return False
log.info("Remounting %s -> %s...", drive, mount)
try:
Expand Down Expand Up @@ -209,37 +233,8 @@ def check_device_bad_blocks() -> bool:
passes = 2 if state.check_bad else 1

# Probe the device's logical sector size so badblocks uses the real
# device geometry. Fall back to 4096 bytes if detection fails.
logical_block_size = 4096
try:
probe = subprocess.run(
[_find_tool("blockdev"), "--getss", drive],
capture_output=True,
text=True,
check=False,
)
if probe.returncode == 0:
probed = probe.stdout.strip()
if probed.isdigit():
logical_block_size = int(probed)
else:
log.warning(
"Unexpected blockdev output for %r: %r. Using default block size.",
drive,
probed,
)
else:
log.warning(
"blockdev failed for %s (exit %d). Using default block size.",
drive,
probe.returncode,
)
except Exception as exc:
log.warning(
"Could not probe sector size for %s: %s. Using default block size.",
drive,
exc,
)
# device geometry. Fall back to 512 bytes if detection fails.
logical_block_size = _get_logical_block_size(drive)

# -s = show progress, -v = verbose output
# -n = non-destructive read-write test (safe default)
Expand Down Expand Up @@ -320,30 +315,10 @@ def _status(msg: str) -> None:
"NTFS",
"ntfs-3g",
),
1: (
"mkfs.vfat",
lambda: ["-I", "-s", str(sectors_per_cluster), "-F", "32", raw_device],
"FAT32",
"dosfstools",
),
2: (
"mkfs.exfat",
lambda: ["-b", str(block_size), raw_device],
"exFAT",
"exfatprogs or exfat-utils",
),
3: (
"mkfs.ext4",
lambda: ["-b", str(block_size), raw_device],
"ext4",
"e2fsprogs",
),
4: (
"mkudffs",
lambda: ["--blocksize=" + str(sector_size), raw_device],
"UDF",
"udftools",
),
1: ("mkfs.vfat", lambda: ["-I", "-s", str(sectors_per_cluster), "-F", "32", raw_device], "FAT32", "dosfstools"),
2: ("mkfs.exfat", lambda: ["-b", str(block_size), raw_device], "exFAT", "exfatprogs or exfat-utils"),
3: ("mkfs.ext4", lambda: ["-b", str(block_size), raw_device], "ext4", "e2fsprogs"),
4: ("mkudffs", lambda: ["--blocksize=" + str(sector_size), raw_device], "UDF", "udftools"),
}

if fs_type not in fs_configs:
Expand Down Expand Up @@ -391,30 +366,14 @@ def _apply_partition_scheme(drive: str) -> None:
# GPT — used for UEFI targets
subprocess.run([_find_tool("parted"), "-s", raw_device, "mklabel", "gpt"], check=True)
subprocess.run(
[
_find_tool("parted"),
"-s",
raw_device,
"mkpart",
"primary",
"1MiB",
"100%",
],
[_find_tool("parted"), "-s", raw_device, "mkpart", "primary", "1MiB", "100%"],
check=True,
)
else:
# MBR — used for BIOS/legacy targets
subprocess.run([_find_tool("parted"), "-s", raw_device, "mklabel", "msdos"], check=True)
subprocess.run(
[
_find_tool("parted"),
"-s",
raw_device,
"mkpart",
"primary",
"1MiB",
"100%",
],
[_find_tool("parted"), "-s", raw_device, "mkpart", "primary", "1MiB", "100%"],
check=True,
)
log.info("Partition scheme %s applied to %s.", scheme_name, raw_device)
Expand All @@ -428,11 +387,6 @@ def _apply_partition_scheme(drive: str) -> None:


def drive_repair() -> None:
# todo:
# add smartctl check if possible
# use fsck to prevent deletion of files for repair
# use testdisk for partition recovery if possible
# do dd if=/dev/zero of=/dev/sdX bs=1M count=10 conv=notrunc before sfdisk use
_, drive, _ = _get_mount_and_drive()
if not drive:
log.error("No drive node found. Cannot repair.")
Expand Down
Loading
Loading