diff --git a/.gitignore b/.gitignore index 2c837349..4826f50d 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,8 @@ qrc_resources.cpp repoeditor/.qtc_clangd/** repoeditor/bin/** repoeditor/build/** +result +result/** sudo/*.o sudo/moc*.* sudo/octopi-sudo diff --git a/README.md b/README.md index 09b9f69e..4aca57c3 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,21 @@ $ make $ sudo make install ``` +### Steps to build Octopi source code (Nix) + +For development in a clean, isolated environment without installing dependencies system-wide, you can use Nix. This requires [Nix with flakes enabled](https://nixos.wiki/wiki/Flakes). + +``` +$ cd octopi +$ nix develop # or use direnv +$ mkdir -p build && cd build +$ cmake .. -DCMAKE_BUILD_TYPE=Release +$ make -j$(nproc) +$ ./octopi +``` + +The Nix environment provides all dependencies (`qt6`, `cmake`, `vala`, etc.) and builds `alpm_octopi_utils` and `qt-sudo` automatically. You can then follow the standard CMake or qmake build instructions above. The environment sets `OCTOPI_ALLOWED_PATHS` to allow running from the build directory. + ### To run Octopi ``` diff --git a/cachecleaner/main.cpp b/cachecleaner/main.cpp index 7f10ebb2..46848bbd 100644 --- a/cachecleaner/main.cpp +++ b/cachecleaner/main.cpp @@ -28,6 +28,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include #include #include +#include int main( int argc, char *argv[] ) { @@ -70,7 +71,7 @@ int main( int argc, char *argv[] ) return (-4); } - if (!QFile::exists(ctn_OCTOPISUDO)) + if (QStandardPaths::findExecutable(QStringLiteral("qt-sudo")).isEmpty()) { qDebug() << "Aborting cache-cleaner as 'octopi-sudo' binary could not be found! [" << ctn_OCTOPISUDO << "]"; return (-5); diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..6ed149dd --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1762977756, + "narHash": "sha256-4PqRErxfe+2toFJFgcRKZ0UI9NSIOJa+7RXVtBhy4KE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "c5ae371f1a6a7fd27823bc500d9390b38c05fa55", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..eef15d76 --- /dev/null +++ b/flake.nix @@ -0,0 +1,89 @@ +{ + # Maintenance: Run 'nix flake update' to update nixpkgs. For alpm_octopi_utils and qt-sudo, + # check GitHub for new commits/tags and update rev + sha256 (get hash from error output). + description = "Octopi Nix development environment"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { + self, + nixpkgs, + flake-utils, + }: + flake-utils.lib.eachDefaultSystem ( + system: let + pkgs = nixpkgs.legacyPackages.${system}; + + alpm_octopi_utils = pkgs.stdenv.mkDerivation { + pname = "alpm_octopi_utils"; + version = "unstable-2024-12-27"; + + src = pkgs.fetchFromGitHub { + owner = "aarnt"; + repo = "alpm_octopi_utils"; + rev = "0ed2a8bd6b869f40683cf7a79727dc64d7da274e"; + sha256 = "sha256-TxjXtdhp2BqkGDVYnyhQ5mccU0MDndFfAZMuBQRVf1c="; + }; + + nativeBuildInputs = with pkgs; [cmake vala pkg-config]; + buildInputs = with pkgs; [pacman glib libarchive]; + }; + + qt-sudo = pkgs.stdenv.mkDerivation { + pname = "qt-sudo"; + version = "2.2.0"; + + src = pkgs.fetchFromGitHub { + owner = "aarnt"; + repo = "qt-sudo"; + rev = "v2.2.0"; + sha256 = "sha256-bvRQEOHMnmAr95ecHkVD0JWGiCMTCx51ncMwrS3/wu0="; + }; + + nativeBuildInputs = with pkgs; [qt6.wrapQtAppsHook qt6.qmake qt6.qttools]; + buildInputs = with pkgs; [qt6.qtbase]; + + postConfigure = '' + substituteInPlace Makefile \ + --replace-quiet "${pkgs.qt6.qtbase}/bin/lrelease" "${pkgs.qt6.qttools}/bin/lrelease" + ''; + }; + in { + devShells.default = pkgs.mkShell { + buildInputs = with pkgs; [ + alpm_octopi_utils + cmake + gcc + git + glib + gnumake + libarchive + lxqt.qtermwidget + pacman + pipewire + pkg-config + qt-sudo + qt6.qmake + qt6.qt5compat + qt6.qtbase + qt6.qtmultimedia + qt6.qttools + qt6.wrapQtAppsHook + sudo + vala + ]; + + shellHook = '' + export OCTOPI_ALLOWED_PATHS="/usr/bin:$PWD/build" + export LD_LIBRARY_PATH="${pkgs.pipewire}/lib:$LD_LIBRARY_PATH" + ''; + + QT_QPA_PLATFORM_PLUGIN_PATH = "${pkgs.qt6.qtbase}/${pkgs.qt6.qtbase.qtPluginPrefix}"; + QT_PLUGIN_PATH = "${pkgs.qt6.qtbase}/${pkgs.qt6.qtbase.qtPluginPrefix}"; + }; + } + ); +} diff --git a/notifier/main.cpp b/notifier/main.cpp index 6dedfe23..dfb4361a 100644 --- a/notifier/main.cpp +++ b/notifier/main.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #define NO_GTK_STYLE @@ -65,7 +66,7 @@ int main(int argc, char *argv[]) return (-3); } - if (!QFile::exists(ctn_OCTOPISUDO)) + if (QStandardPaths::findExecutable(QStringLiteral("qt-sudo")).isEmpty()) { qDebug() << "Aborting notifier as 'qt-sudo' binary could not be found! [" << ctn_OCTOPISUDO << "]"; return (-4); diff --git a/repoeditor/main.cpp b/repoeditor/main.cpp index c207a43f..9cd8e74b 100644 --- a/repoeditor/main.cpp +++ b/repoeditor/main.cpp @@ -30,6 +30,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #include #include #include +#include int main( int argc, char *argv[] ) { @@ -62,7 +63,7 @@ int main( int argc, char *argv[] ) return (-2); } - if (!QFile::exists(ctn_OCTOPISUDO)) + if (QStandardPaths::findExecutable(QStringLiteral("qt-sudo")).isEmpty()) { qDebug() << "Aborting Repository Editor as 'octopi-sudo' binary could not be found! [" << ctn_OCTOPISUDO << "]"; return (-3); diff --git a/src/constants.h b/src/constants.h index 44ac1801..41edea58 100644 --- a/src/constants.h +++ b/src/constants.h @@ -214,7 +214,7 @@ const int ctn_RUN_IN_TERMINAL(328); const QString ctn_NO_SU_COMMAND(QStringLiteral("none")); const QString ctn_ROOT_SH(QStringLiteral("/bin/sh -c ")); -const QString ctn_OCTOPISUDO(QStringLiteral("/usr/local/bin/qt-sudo")); +const QString ctn_OCTOPISUDO(QStringLiteral("qt-sudo")); const QString ctn_OCTOPISUDO_PARAMS(QStringLiteral("-d")); const QString ctn_KDE_DESKTOP(QStringLiteral("kwin")); diff --git a/src/main.cpp b/src/main.cpp index 14d341d5..64d9a576 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,6 +27,7 @@ #include "QtSolutions/qtsingleapplication.h" #include #include +#include int main(int argc, char *argv[]) { @@ -42,7 +43,7 @@ int main(int argc, char *argv[]) return (-2); } - if (!QFile::exists(ctn_OCTOPISUDO)) + if (QStandardPaths::findExecutable(QStringLiteral("qt-sudo")).isEmpty()) { qDebug() << "Aborting octopi as 'qt-sudo' binary could not be found! [" << ctn_OCTOPISUDO << "]"; return (-3); diff --git a/src/unixcommand.cpp b/src/unixcommand.cpp index 09fb2e7c..d232e4c1 100644 --- a/src/unixcommand.cpp +++ b/src/unixcommand.cpp @@ -35,6 +35,7 @@ #include #include #include +#include /* * Collection of methods to execute many Unix commands @@ -701,14 +702,19 @@ bool UnixCommand::doInternetPingTest() } /* - * Checks if the given executable is available somewhere in the system + * Finds the given executable in PATH + */ +QString UnixCommand::findExecutable(const QString& exeName) +{ + return QStandardPaths::findExecutable(exeName); +} + +/* + * Checks if the given executable is available */ bool UnixCommand::hasTheExecutable(const QString& exeName) { - if (exeName == ctn_OCTOPISUDO) - return (QFile::exists(ctn_OCTOPISUDO)); - else - return (QFile::exists(QStringLiteral("/usr/bin/") + exeName)); + return !findExecutable(exeName).isEmpty(); } /* @@ -1180,10 +1186,13 @@ bool UnixCommand::isAppRunning(const QString &appName, bool justOneInstance) } /* - * Checks if Octopi/Octopi-notifier, cache-cleaner, etc is being executed from /usr/bin + * Checks if Octopi/Octopi-notifier, cache-cleaner, etc is being executed from allowed paths + * Allowed paths can be configured via OCTOPI_ALLOWED_PATHS environment variable (colon-separated) + * Default: /usr/bin only (security: only system-installed binaries are trusted) + * Development: set OCTOPI_ALLOWED_PATHS=/usr/bin:/path/to/your/build to allow running from build dir */ bool UnixCommand::isOctoToolRunning(const QString &octoToolName) -{ +{ char exePath[PATH_MAX]; ssize_t count = readlink("/proc/self/exe", exePath, sizeof(exePath)); if (count == -1) @@ -1195,6 +1204,18 @@ bool UnixCommand::isOctoToolRunning(const QString &octoToolName) QString path = QString::fromUtf8(exePath, count); QFileInfo fi(path); + QByteArray allowedPathsEnv = qgetenv("OCTOPI_ALLOWED_PATHS"); + if (!allowedPathsEnv.isEmpty()) + { + QStringList allowedPaths = QString::fromUtf8(allowedPathsEnv).split(QLatin1Char(':'), Qt::SkipEmptyParts); + for (const QString &allowedPath : allowedPaths) + { + if (fi.absoluteFilePath() == allowedPath + QLatin1Char('/') + octoToolName) + return true; + } + return false; + } + return (fi.absoluteFilePath() == QLatin1String("/usr/bin/") + octoToolName); } diff --git a/src/unixcommand.h b/src/unixcommand.h index 3681c0ff..5d925ab8 100644 --- a/src/unixcommand.h +++ b/src/unixcommand.h @@ -111,6 +111,7 @@ class UnixCommand : public QObject{ static bool doInternetPingTest(); static bool isTextFile(QString fileName); //fileName is Path + Name + static QString findExecutable( const QString& exeName ); static bool hasTheExecutable( const QString& exeName ); static bool isAppRunning(const QString &appName, bool justOneInstance = false); diff --git a/src/wmhelper.cpp b/src/wmhelper.cpp index 225699a7..dc3069ef 100644 --- a/src/wmhelper.cpp +++ b/src/wmhelper.cpp @@ -28,6 +28,8 @@ #include #include #include +#include +#include /* * This class is a helper to abstract some Desktop Environments services for Octopi. @@ -226,7 +228,8 @@ QString WMHelper::getXFCEEditor() */ QString WMHelper::getOctopiSudoCommand() { - QString result = ctn_OCTOPISUDO; + QString result = UnixCommand::findExecutable(ctn_OCTOPISUDO); + result += QStringLiteral(" "); result += ctn_OCTOPISUDO_PARAMS; return result; @@ -237,13 +240,13 @@ QString WMHelper::getOctopiSudoCommand() */ QString WMHelper::getSUCommand() { - QString result(ctn_NO_SU_COMMAND); + QString qtSudoPath = UnixCommand::findExecutable(ctn_OCTOPISUDO); - if (QFile::exists(ctn_OCTOPISUDO)){ - result = ctn_OCTOPISUDO; + if (!qtSudoPath.isEmpty() && QFile::exists(qtSudoPath)){ + return qtSudoPath; } - return result; + return ctn_NO_SU_COMMAND; } /* @@ -251,13 +254,13 @@ QString WMHelper::getSUCommand() */ QString WMHelper::getSUTool() { - QString result(QLatin1String("")); + QString qtSudoPath = UnixCommand::findExecutable(ctn_OCTOPISUDO); - if (QFile::exists(ctn_OCTOPISUDO)){ - return ctn_OCTOPISUDO; + if (!qtSudoPath.isEmpty() && QFile::exists(qtSudoPath)){ + return qtSudoPath; } - return result; + return QLatin1String(""); } /*