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

on:
# Triggers the workflow on push or pull request events but only for the main branch
push:
branches: [ main ]
pull_request:
branches: [ main ]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

env:
BUILD_TYPE: RelWithDebInfo

jobs:
build-appimage:
runs-on: ubuntu-22.04

steps:
- uses: actions/checkout@v4

- name: Update Package Database
run: sudo apt-get update

- name: Install Dependencies
run: sudo apt-get install libgtkmm-3.0-dev gettext wget file

- name: Download linuxdeploy
run: |
wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
chmod +x linuxdeploy-x86_64.AppImage
sudo mv linuxdeploy-x86_64.AppImage /usr/local/bin/linuxdeploy

- name: Download appimagetool
run: |
wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
Comment on lines +29 to +40

Copilot AI Mar 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow downloads linuxdeploy and appimagetool from continuous without checksum verification, which is a supply-chain risk and doesn’t match the PR description (pinned versions + SHA256 verification). Pin the exact release assets and verify SHA256 before installing them into /usr/local/bin.

Suggested change
# Note: Using 'continuous' release for latest version. For reproducible builds,
# pin to a specific release tag and verify SHA256 checksum.
run: |
wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
chmod +x linuxdeploy-x86_64.AppImage
sudo mv linuxdeploy-x86_64.AppImage /usr/local/bin/linuxdeploy
- name: Download appimagetool
# Note: Using 'continuous' release for latest version. For reproducible builds,
# pin to a specific release tag and verify SHA256 checksum.
run: |
wget https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
# Download a pinned linuxdeploy release and verify its SHA256 checksum.
run: |
LINUXDEPLOY_VERSION="1-alpha-20201231-1"
# TODO: When bumping LINUXDEPLOY_VERSION, update LINUXDEPLOY_SHA256 accordingly.
LINUXDEPLOY_SHA256="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
wget -O linuxdeploy-x86_64.AppImage \
"https://github.com/linuxdeploy/linuxdeploy/releases/download/${LINUXDEPLOY_VERSION}/linuxdeploy-x86_64.AppImage"
echo "${LINUXDEPLOY_SHA256} linuxdeploy-x86_64.AppImage" | sha256sum -c -
chmod +x linuxdeploy-x86_64.AppImage
sudo mv linuxdeploy-x86_64.AppImage /usr/local/bin/linuxdeploy
- name: Download appimagetool
# Download a pinned appimagetool release and verify its SHA256 checksum.
run: |
APPIMAGETOOL_VERSION="13"
# TODO: When bumping APPIMAGETOOL_VERSION, update APPIMAGETOOL_SHA256 accordingly.
APPIMAGETOOL_SHA256="bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
wget -O appimagetool-x86_64.AppImage \
"https://github.com/AppImage/AppImageKit/releases/download/${APPIMAGETOOL_VERSION}/appimagetool-x86_64.AppImage"
echo "${APPIMAGETOOL_SHA256} appimagetool-x86_64.AppImage" | sha256sum -c -

Copilot uses AI. Check for mistakes.
chmod +x appimagetool-x86_64.AppImage
sudo mv appimagetool-x86_64.AppImage /usr/local/bin/appimagetool
Comment thread
ashaduri marked this conversation as resolved.

- name: Create Build Directory
run: cmake -E make_directory build-appimage

- name: Configure CMake for AppImage
shell: bash
working-directory: ${{github.workspace}}/build-appimage
env:
CC: gcc-11
CXX: g++-11
run: >
cmake $GITHUB_WORKSPACE
-DCMAKE_BUILD_TYPE=$BUILD_TYPE
-DCMAKE_INSTALL_PREFIX=/usr
-DAPP_BUILD_APPIMAGE=ON
-DCMAKE_TOOLCHAIN_FILE="$GITHUB_WORKSPACE/toolchains/linux-dev.cmake"

- name: Build
shell: bash
working-directory: ${{github.workspace}}/build-appimage
run: cmake --build . --config $BUILD_TYPE -j$(nproc)

- name: Install to AppDir
shell: bash
working-directory: ${{github.workspace}}/build-appimage
run: DESTDIR=${{github.workspace}}/AppDir cmake --install .

- name: Setup AppDir structure
shell: bash
working-directory: ${{github.workspace}}
run: |
# Copy desktop file and icon to AppDir root
cp AppDir/usr/share/applications/dev.shaduri.gsmartcontrol.desktop AppDir/
cp AppDir/usr/share/icons/hicolor/256x256/apps/gsmartcontrol.png AppDir/

# Create AppRun script
cat > AppDir/AppRun << 'EOF'
#!/bin/bash
SELF=$(readlink -f "$0")
HERE=${SELF%/*}
export LD_LIBRARY_PATH="$HERE/usr/lib:${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
export PATH="$HERE/usr/bin:${PATH:+:$PATH}"
export GSETTINGS_SCHEMA_DIR="$HERE/usr/share/glib-2.0/schemas:${GSETTINGS_SCHEMA_DIR:+:$GSETTINGS_SCHEMA_DIR}"
export GDK_PIXBUF_MODULEDIR="$HERE/usr/lib/gdk-pixbuf-2.0/2.10.0/loaders"
export GDK_PIXBUF_MODULE_FILE="$HERE/usr/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache"
exec "$HERE/usr/bin/gsmartcontrol" "$@"
EOF
chmod +x AppDir/AppRun

- name: Bundle dependencies with linuxdeploy
shell: bash
working-directory: ${{github.workspace}}
run: |
linuxdeploy --appdir AppDir \
--executable AppDir/usr/bin/gsmartcontrol \
--desktop-file AppDir/dev.shaduri.gsmartcontrol.desktop \
--icon-file AppDir/gsmartcontrol.png

- name: Update GDK pixbuf cache
shell: bash
working-directory: ${{github.workspace}}
continue-on-error: true
run: |
if [ -d "AppDir/usr/lib/gdk-pixbuf-2.0" ]; then
gdk-pixbuf-query-loaders > AppDir/usr/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache || true
fi

- name: Get version
id: version
shell: bash
run: |
VERSION=$(grep "CMAKE_PROJECT_VERSION" version.txt | cut -d'=' -f2 | tr -d ' ')
Comment thread
ashaduri marked this conversation as resolved.
Outdated
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
echo "Version: $VERSION"

- name: Create AppImage
shell: bash
working-directory: ${{github.workspace}}
run: |
ARCH=x86_64 appimagetool AppDir GSmartControl-${{ steps.version.outputs.VERSION }}-x86_64.AppImage

- name: Upload AppImage artifact
uses: actions/upload-artifact@v4
with:
name: GSmartControl AppImage
path: ${{github.workspace}}/GSmartControl-*-x86_64.AppImage
10 changes: 10 additions & 0 deletions .obs/workflows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,16 @@ build_main_workflow:
- x86_64
- i586

# AppImage build (using Ubuntu 22.04 as base)
- name: AppImage
paths:
- target_project: openSUSE:Tools
target_repository: xUbuntu_22.04
- target_project: Ubuntu:22.04
target_repository: universe
architectures:
- x86_64

- set_flags:
flags:
- type: publish
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
# User-controlled build options
option(APP_BUILD_EXAMPLES "Build examples" OFF)
option(APP_BUILD_TESTS "Build tests" OFF)
option(APP_BUILD_APPIMAGE "Build for AppImage (portable data file paths)" OFF)


# Install documentation
Expand Down
25 changes: 20 additions & 5 deletions data/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@

# Generate files
configure_file("gsmartcontrol.appdata.in.xml" "gsmartcontrol.appdata.xml" ESCAPE_QUOTES @ONLY)
configure_file("gsmartcontrol.in.desktop" "gsmartcontrol.desktop" ESCAPE_QUOTES @ONLY)
if (APP_BUILD_APPIMAGE)
set(APPIMAGE_EXEC_LINE "Exec=\"@CMAKE_INSTALL_FULL_BINDIR@/gsmartcontrol\"")
configure_file("gsmartcontrol.in.desktop" "dev.shaduri.gsmartcontrol.desktop" ESCAPE_QUOTES @ONLY)
else()
set(APPIMAGE_EXEC_LINE "Exec=\"@CMAKE_INSTALL_FULL_BINDIR@/gsmartcontrol-root\"")
Comment thread
ashaduri marked this conversation as resolved.
Outdated
configure_file("gsmartcontrol.in.desktop" "gsmartcontrol.desktop" ESCAPE_QUOTES @ONLY)
Comment on lines +13 to +18

Copilot AI Mar 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APPIMAGE_EXEC_LINE is set with embedded double-quotes, but configure_file(... ESCAPE_QUOTES ...) will escape those quotes when generating the .desktop file. This likely produces Exec=\"/usr/bin/gsmartcontrol\" (literal backslashes), which can break desktop launching. Prefer setting the variable without quotes (or remove ESCAPE_QUOTES for the desktop file) and keep quoting (if any) in the template line itself.

Suggested change
configure_file("gsmartcontrol.in.desktop" "dev.shaduri.gsmartcontrol.desktop" ESCAPE_QUOTES @ONLY)
else()
set(APPIMAGE_DESKTOP_ID "gsmartcontrol")
set(APPIMAGE_EXEC_LINE "Exec=\"${CMAKE_INSTALL_FULL_BINDIR}/gsmartcontrol-root\"")
configure_file("gsmartcontrol.appdata.in.xml" "gsmartcontrol.appdata.xml" ESCAPE_QUOTES @ONLY)
configure_file("gsmartcontrol.in.desktop" "gsmartcontrol.desktop" ESCAPE_QUOTES @ONLY)
configure_file("gsmartcontrol.in.desktop" "dev.shaduri.gsmartcontrol.desktop" @ONLY)
else()
set(APPIMAGE_DESKTOP_ID "gsmartcontrol")
set(APPIMAGE_EXEC_LINE "Exec=\"${CMAKE_INSTALL_FULL_BINDIR}/gsmartcontrol-root\"")
configure_file("gsmartcontrol.appdata.in.xml" "gsmartcontrol.appdata.xml" ESCAPE_QUOTES @ONLY)
configure_file("gsmartcontrol.in.desktop" "gsmartcontrol.desktop" @ONLY)

Copilot uses AI. Check for mistakes.
endif()
configure_file("gsmartcontrol-root.in.sh" "gsmartcontrol-root.sh" ESCAPE_QUOTES @ONLY)
configure_file("org.gsmartcontrol.in.policy" "org.gsmartcontrol.policy" ESCAPE_QUOTES @ONLY)

Expand Down Expand Up @@ -42,8 +48,13 @@ endif()

# Desktop file
if (NOT WIN32)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/gsmartcontrol.desktop"
DESTINATION "${CMAKE_INSTALL_DATADIR}/applications/")
if (APP_BUILD_APPIMAGE)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/dev.shaduri.gsmartcontrol.desktop"
DESTINATION "${CMAKE_INSTALL_DATADIR}/applications/")
else()
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/gsmartcontrol.desktop"
DESTINATION "${CMAKE_INSTALL_DATADIR}/applications/")
endif()

# Appdata file
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/gsmartcontrol.appdata.xml"
Expand All @@ -55,9 +66,13 @@ if (NOT WIN32)

# Man pages
install(FILES "man1/gsmartcontrol.1" DESTINATION "${CMAKE_INSTALL_MANDIR}/man1")
install(FILES "man1/gsmartcontrol.1" DESTINATION "${CMAKE_INSTALL_MANDIR}/man1" RENAME "gsmartcontrol-root.1")
if (NOT APP_BUILD_APPIMAGE)
install(FILES "man1/gsmartcontrol.1" DESTINATION "${CMAKE_INSTALL_MANDIR}/man1" RENAME "gsmartcontrol-root.1")
endif()

# Scripts (this goes to bin, not sbin, as it doesn't require root privileges before running)
install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/gsmartcontrol-root.sh" TYPE BIN RENAME "gsmartcontrol-root")
if (NOT APP_BUILD_APPIMAGE)
install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/gsmartcontrol-root.sh" TYPE BIN RENAME "gsmartcontrol-root")
endif()
endif()

4 changes: 2 additions & 2 deletions data/gsmartcontrol.appdata.in.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop">
<id>gsmartcontrol</id>
<id>dev.shaduri.gsmartcontrol</id>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0</project_license>
<name>GSmartControl</name>
Expand All @@ -14,7 +14,7 @@
on it.
</p>
</description>
<launchable type="desktop-id">gsmartcontrol.desktop</launchable>
<launchable type="desktop-id">dev.shaduri.gsmartcontrol.desktop</launchable>
Comment thread
ashaduri marked this conversation as resolved.
Outdated
<url type="homepage">https://gsmartcontrol.shaduri.dev</url>
<screenshots>
<screenshot type="default">
Expand Down
3 changes: 2 additions & 1 deletion data/gsmartcontrol.in.desktop
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,5 @@ Icon=gsmartcontrol
#X-KDE-RootOnly=true

# Run with root permissions.
Exec="@CMAKE_INSTALL_FULL_BINDIR@/gsmartcontrol-root"
# For AppImage builds, execute the binary directly (AppImage does not support embedded sudo scripts)
@APPIMAGE_EXEC_LINE@
106 changes: 106 additions & 0 deletions packaging/appimage/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# GSmartControl AppImage Build

This directory contains scripts and configurations for building GSmartControl as an AppImage.

## What is AppImage?

AppImage is a format for distributing portable software on Linux without requiring installation. AppImages are self-contained applications that can run on most Linux distributions.

## Building the AppImage

### Prerequisites

You need to have the following installed:
- cmake (>= 3.14)
- g++ or clang with C++20 support
- GTK3 and gtkmm-3.0 development packages
- smartmontools (for runtime)
- linuxdeploy and appimagetool (the script will download them if not available)

On Ubuntu/Debian:
```bash
sudo apt-get install cmake g++ libgtkmm-3.0-dev gettext smartmontools
```

On Fedora:
```bash
sudo dnf install cmake gcc-c++ gtkmm30-devel gettext smartmontools
```

### Build Steps

From the repository root directory, run:

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

The script will:
1. Configure CMake with `-DAPP_BUILD_APPIMAGE=ON`
2. Build the application
3. Create an AppDir with the proper structure
4. Bundle dependencies using linuxdeploy
5. Create the final AppImage file

### Output

The script creates an AppImage file named `GSmartControl-<version>-<arch>.AppImage` in the repository root.

## Running the AppImage

**Important:** GSmartControl requires root privileges to access disk drives. You must run the AppImage with sudo:

```bash
sudo ./GSmartControl-<version>-x86_64.AppImage
```

### Why sudo is required?

- AppImage format does not support embedded privilege escalation scripts
- The binary must be executed directly as root to access `/dev/sd*` and `/dev/nvme*` devices
- This is different from traditional package installations which use the `gsmartcontrol-root` wrapper script

## AppImage-Specific Changes

When building with `-DAPP_BUILD_APPIMAGE=ON`, the following changes are applied:

1. **Data file paths**: Uses relative paths from binary location (`bin/../share/...`)
2. **Desktop file**:
- Uses rDNS format: `dev.shaduri.gsmartcontrol.desktop`
- Executes binary directly without `gsmartcontrol-root` wrapper
3. **Binary location**: Installed to `bin/` instead of `sbin/`
4. **No wrapper script**: `gsmartcontrol-root` is not installed

## Compatibility

The AppImage should work on:
- Fedora Atomic (Silverblue, Kinoite, etc.)
- Most modern Linux distributions with GTK3 support
- Systems with read-only root filesystems

Tested architectures:
- x86_64 (Intel/AMD 64-bit)

## Troubleshooting

### "Permission denied" when running AppImage
Make the AppImage executable:
```bash
chmod +x GSmartControl-*.AppImage
```

### "No drives detected"
Make sure to run with sudo:
```bash
sudo ./GSmartControl-*.AppImage
```

### GTK theme issues
You may need to install GTK3 themes on your system. The AppImage bundles the necessary libraries but uses system themes when available.

## Notes for Developers

- The AppImage build uses the same source code as regular builds
- The `BuildEnv::is_appimage_build()` function can be used to detect AppImage builds at runtime
- Icon and UI file paths are resolved at runtime relative to the binary location
- The AppImage includes all necessary GTK3 and gtkmm libraries
Loading
Loading