Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
92 changes: 92 additions & 0 deletions .github/workflows/build-appimage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
name: Build AppImage

on:
workflow_dispatch:
push:
tags:
- "v*"

permissions:
contents: read

jobs:
build:
name: Build WoeUSB-ng AppImage
runs-on: ubuntu-latest
timeout-minutes: 90

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Resolve package version
id: version
shell: bash
run: |
set -euo pipefail

version="$(packaging/appimage/build-appimage.sh --print-version)"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "Building WoeUSB-ng v${version}"

- name: Restore RPM cache
uses: actions/cache@v4
with:
path: build/appimage/deps-rpms
key: woeusb-ng-appimage-rpms-${{ runner.os }}-${{ steps.version.outputs.version }}-${{ hashFiles('packaging/appimage/build-appimage.sh') }}
restore-keys: |
woeusb-ng-appimage-rpms-${{ runner.os }}-${{ steps.version.outputs.version }}-
woeusb-ng-appimage-rpms-${{ runner.os }}-

- name: Build AppImage in Fedora
shell: bash
run: |
set -euo pipefail

docker run --rm \
-e HOST_UID="$(id -u)" \
-e HOST_GID="$(id -g)" \
-e WOEUSB_VERSION="${{ steps.version.outputs.version }}" \
-v "$PWD":/work \
-w /work \
fedora:latest \
bash -lc '
set -euo pipefail

trap "chown -R ${HOST_UID}:${HOST_GID} /work/build 2>/dev/null || true" EXIT

dnf install -y \
--setopt=install_weak_deps=False \
--disablerepo=fedora-cisco-openh264 \
git wget python3 python3-pip python3-wxpython4 \
patchelf rpm-build cpio file findutils binutils dnf-plugins-core

packaging/appimage/build-appimage.sh --no-clean

app="build/appimage/WoeUSB-ng-${WOEUSB_VERSION}-x86_64.AppImage"
test -s "$app"

APPIMAGE_EXTRACT_AND_RUN=1 "$app" --version | tee build/appimage/appimage-version.txt
grep -Fx "$WOEUSB_VERSION" build/appimage/appimage-version.txt

APPIMAGE_EXTRACT_AND_RUN=1 "$app" --help >/dev/null
'

- name: Upload AppImage artifact
uses: actions/upload-artifact@v4
with:
name: WoeUSB-ng-${{ steps.version.outputs.version }}-x86_64.AppImage
path: build/appimage/WoeUSB-ng-${{ steps.version.outputs.version }}-x86_64.AppImage
if-no-files-found: error
retention-days: 14

- name: Write job summary
shell: bash
run: |
{
echo "## AppImage build"
echo
echo "- Version: \`${{ steps.version.outputs.version }}\`"
echo "- Artifact: \`WoeUSB-ng-${{ steps.version.outputs.version }}-x86_64.AppImage\`"
echo "- Retention: 14 days"
} >> "$GITHUB_STEP_SUMMARY"
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ venv/
dist/

build/
*.AppImage
*.AppImage.zsync

create_tarball
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,32 @@ This project rewrite of original [WoeUSB](https://github.com/slacka/WoeUSB)

## Installation

### AppImage

Portable x86_64 AppImages, when available, are attached to the GitHub Releases page.
They bundle WoeUSB-ng, Python, wxPython/GTK, and the runtime tools needed for disk
creation.

```shell
chmod +x WoeUSB-ng-*-x86_64.AppImage
./WoeUSB-ng-*-x86_64.AppImage
```

For command-line usage:

```shell
sudo ./WoeUSB-ng-*-x86_64.AppImage --cli --device /path/to/windows.iso /dev/sdX
```

Replace `/dev/sdX` with the target USB device. All data on the target device will
be erased.

AppImage builds can also be produced from source with:

```shell
packaging/appimage/build-appimage.sh
```

### Arch
```shell
yay -S woeusb-ng
Expand Down
129 changes: 129 additions & 0 deletions packaging/appimage/AppRun
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#!/bin/bash
set -euo pipefail

APPDIR=$(dirname "$(readlink -f "$0")")

export LD_LIBRARY_PATH="$APPDIR/usr/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
export PATH="$APPDIR/usr/bin:$PATH"

PYTHON_VERSION=$("$APPDIR/usr/bin/python3" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null || echo "3")
export PYTHONHOME="$APPDIR/usr"
PYTHONPATH_BUNDLED="$APPDIR/usr/lib/python3/site-packages:$APPDIR/usr/lib/python${PYTHON_VERSION}:$APPDIR/usr/lib/python${PYTHON_VERSION}/lib-dynload"
export PYTHONPATH="$PYTHONPATH_BUNDLED${PYTHONPATH:+:$PYTHONPATH}"

export GTK_DATA_PREFIX="$APPDIR/usr"
export GTK_THEME=Adwaita
export GTK_PATH="$APPDIR/usr/lib/gtk-3.0"
export GTK3_MODULES=""
export GDK_PIXBUF_MODULE_FILE="$APPDIR/usr/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache"
export GDK_PIXBUF_MODULEDIR="$APPDIR/usr/lib/gdk-pixbuf-2.0/2.10.0/loaders"
export PANGO_LIBDIR="$APPDIR/usr/lib"
export GSETTINGS_SCHEMA_DIR="$APPDIR/usr/share/glib-2.0/schemas"
export GSETTINGS_BACKEND=memory
export XDG_DATA_DIRS="$APPDIR/usr/share:${XDG_DATA_DIRS:-/usr/local/share:/usr/share}"
export FONTCONFIG_PATH="${FONTCONFIG_PATH:-/etc/fonts}"
export DBUS_SESSION_BUS_ADDRESS="${DBUS_SESSION_BUS_ADDRESS:-}"

if [ -d "$APPDIR/usr/lib/grub" ]; then
export GRUB_PREFIX="$APPDIR/usr/lib/grub"
fi

run_cli() {
exec "$APPDIR/usr/bin/python3" -W ignore::SyntaxWarning -c "
import sys
sys.argv = ['woeusb'] + sys.argv[1:]
from WoeUSB import core
core.run()
" "$@"
}

run_about() {
exec "$APPDIR/usr/bin/python3" -W ignore::SyntaxWarning -c "
from WoeUSB import core
core.print_application_info()
"
}

check_wx() {
local wx_import_error

if ! wx_import_error=$("$APPDIR/usr/bin/python3" -c "import wx; import wx.adv" 2>&1); then
echo "ERROR: Failed to import wxPython. The AppImage may be corrupted." >&2
echo "$wx_import_error" >&2
echo "Try downloading the AppImage again or report this build as broken." >&2
exit 1
fi
}

NEEDS_ROOT=1
for arg in "$@"; do
case "$arg" in
--help|-h|--version|-V|--about|-ab)
NEEDS_ROOT=0
break
;;
esac
done

if [ "${1:-}" = "--cli" ] && [ "$NEEDS_ROOT" -eq 0 ]; then
shift
case "${1:-}" in
--about|-ab)
run_about
;;
esac
run_cli "$@"
fi

if [ "${1:-}" != "--cli" ]; then
case "${1:-}" in
--about|-ab)
run_about
;;
--help|-h|--version|-V)
run_cli "$@"
;;
esac

check_wx
fi

if [ "$EUID" -ne 0 ] && [ "$NEEDS_ROOT" -eq 1 ]; then
echo "WoeUSB-ng requires root privileges for disk operations."

if command -v pkexec >/dev/null 2>&1; then
echo "Elevating with pkexec..."
LAUNCHER="${APPIMAGE:-$(readlink -f "$0")}"
exec pkexec env \
DISPLAY="${DISPLAY:-}" \
XAUTHORITY="${XAUTHORITY:-$HOME/.Xauthority}" \
WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-}" \
XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-}" \
DBUS_SESSION_BUS_ADDRESS="${DBUS_SESSION_BUS_ADDRESS:-}" \
"$LAUNCHER" "$@"
elif command -v sudo >/dev/null 2>&1; then
echo "pkexec not found, elevating with sudo..."
LAUNCHER="${APPIMAGE:-$(readlink -f "$0")}"
exec sudo \
DISPLAY="${DISPLAY:-}" \
XAUTHORITY="${XAUTHORITY:-$HOME/.Xauthority}" \
WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-}" \
XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-}" \
DBUS_SESSION_BUS_ADDRESS="${DBUS_SESSION_BUS_ADDRESS:-}" \
"$LAUNCHER" "$@"
else
echo "ERROR: Neither pkexec nor sudo is available." >&2
echo "Re-run as root: sudo $0 $*" >&2
exit 1
fi
fi

if [ "${1:-}" = "--cli" ]; then
shift
run_cli "$@"
fi

exec "$APPDIR/usr/bin/python3" -W ignore::SyntaxWarning -c "
from WoeUSB import gui
gui.run()
" "$@"
66 changes: 66 additions & 0 deletions packaging/appimage/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# WoeUSB-ng AppImage Packaging

This directory contains the tooling used to build a portable x86_64 AppImage
from the current WoeUSB-ng source checkout.

The AppImage bundles:

- WoeUSB-ng
- Python 3 and the Python standard library
- wxPython and the GTK3 runtime needed by the GUI
- Runtime tools used by WoeUSB-ng: parted, GRUB tools, ntfs-3g, dosfstools,
and p7zip

Host utilities such as `mount`, `umount`, `lsblk`, `wipefs`, `blockdev`, and
`df` are still expected from the host system because they operate on live block
devices.

## Build Requirements

The build script is intended to run on Fedora or in a Fedora container. It uses
`dnf download` to collect runtime RPMs before assembling the AppDir.

Install the build dependencies on Fedora:

```shell
sudo dnf install -y --setopt=install_weak_deps=False --disablerepo=fedora-cisco-openh264 \
git wget python3 python3-pip python3-wxpython4 \
patchelf rpm-build cpio file findutils binutils dnf-plugins-core
```

Then build from the repository root:

```shell
packaging/appimage/build-appimage.sh
```

The AppImage will be written to:

```text
build/appimage/WoeUSB-ng-<version>-x86_64.AppImage
```

## Container Build

From any Linux distribution with Docker:

```shell
docker run --rm -v "$PWD":/work -w /work fedora:latest bash -lc \
'dnf install -y --setopt=install_weak_deps=False --disablerepo=fedora-cisco-openh264 git wget python3 python3-pip python3-wxpython4 patchelf rpm-build cpio file findutils binutils dnf-plugins-core && packaging/appimage/build-appimage.sh'
```

With Podman:

```shell
podman run --rm -v "$PWD":/work:Z -w /work fedora:latest bash -lc \
'dnf install -y --setopt=install_weak_deps=False --disablerepo=fedora-cisco-openh264 git wget python3 python3-pip python3-wxpython4 patchelf rpm-build cpio file findutils binutils dnf-plugins-core && packaging/appimage/build-appimage.sh'
```

## Smoke Test

After building:

```shell
APPIMAGE_EXTRACT_AND_RUN=1 build/appimage/WoeUSB-ng-*-x86_64.AppImage --version
APPIMAGE_EXTRACT_AND_RUN=1 build/appimage/WoeUSB-ng-*-x86_64.AppImage --help
```
11 changes: 11 additions & 0 deletions packaging/appimage/WoeUSB-ng.desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[Desktop Entry]
Name=WoeUSB-ng
GenericName=Windows USB Creator
Comment=Create bootable Windows USB drives from ISO images
Exec=WoeUSB-ng
Icon=woeusb-ng
Terminal=false
Type=Application
Categories=Utility;
Keywords=windows;usb;bootable;iso;installer;
StartupNotify=true
Loading