#!/bin/bash # This is Darktable build script for Porteus # Version 2026-08-26 # Copyright 2023-2030, Blaze, Dankov, Russia # All rights reserved. # # Redistribution and use of this script, with or without modification, is # permitted provided that the following conditions are met: # # 1. Redistributions of this script must retain the above copyright # notice, this list of conditions and the following disclaimer. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. export LC_ALL=C export XZ_OPT="-T0" export CMAKE_BUILD_PARALLEL_LEVEL=$(nproc) export CMAKE_POLICY_VERSION_MINIMUM=3.5 [ "$(whoami)" != "root" ] && { echo -e "\nYou need to be root to run this script.\n"; exit 1; } PRGNAM=${PRGNAM:-darktable} BUILD=${BUILD:-1} ARCH=$(uname -m) VERSION=$(lynx -head -dump "https://github.com/darktable-org/darktable/releases/latest" 2>/dev/null | grep -i Location | grep -o "[0-9].*$" | tr -d '\r') SOURCE="https://github.com/darktable-org/darktable/releases/download/release-${VERSION}/darktable-${VERSION}.tar.xz" BOLD="\e[1m"; CYAN="\e[96m"; GREEN="\e[92m"; RED="\e[31m"; YELLOW="\e[93m"; RESET="\e[0m" TMPDIR=/tmp/portch; SRC="$TMPDIR/src"; PKG="$TMPDIR/package-$PRGNAM"; OUTPUT=${OUTPUT:-/tmp}; JOBS=$(nproc) MODULES_TO_DEACTIVATE=() INJECTED_LIBS=() cleanup(){ if [ ${#MODULES_TO_DEACTIVATE[@]} -gt 0 ]; then echo -e "\n${CYAN}Deactivating temporary modules...${RESET}" for mod in "${MODULES_TO_DEACTIVATE[@]}"; do deactivate -q "$mod" >/dev/null 2>&1 rm -f "$mod" done fi rm -f /tmp/*.txz rm -rf "$TMPDIR" } die(){ echo -e "\n${RED}${BOLD}$1${RESET}\n"; cleanup; exit 1; } build_pugixml_module(){ local pugixml_version=$(lynx -head -dump "https://github.com/zeux/pugixml/releases/latest" 2>/dev/null | grep -i Location | grep -o "[0-9].*$" | tr -d '\r') [ -z "$pugixml_version" ] && pugixml_version="1.14" local pugixml_url="https://github.com/zeux/pugixml/releases/download/v${pugixml_version}/pugixml-${pugixml_version}.tar.gz" local pugixml_src="$TMPDIR/pugixml_src" local pugixml_pkg="$TMPDIR/pugixml_pkg" local pugixml_mod="$TMPDIR/pugixml-${pugixml_version}.xzm" echo -e "\n${CYAN}Building pugixml ${pugixml_version} module (static)...${RESET}" mkdir -p "$pugixml_src" "$pugixml_pkg" wget -q "$pugixml_url" -O "$TMPDIR/pugixml.tar.gz" || die "Failed to download pugixml" tar -xf "$TMPDIR/pugixml.tar.gz" -C "$pugixml_src" --strip-components=1 || die "Failed to extract pugixml" rm -f "$TMPDIR/pugixml.tar.gz" cd "$pugixml_src" mkdir -p build && cd build cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF .. || die "pugixml cmake failed" make -j"$JOBS" || die "pugixml build failed" make install DESTDIR="$pugixml_pkg" || die "pugixml install failed" dir2xzm "$pugixml_pkg" "$pugixml_mod" || die "Failed to create pugixml module" echo -e "${GREEN}pugixml module created (static, will be linked into darktable)${RESET}" PUGIXML_MOD_PATH="$pugixml_mod" } copy_libs_to_pkg(){ local lib_name="$1" local mod_path="$2" local tmp_mount="$TMPDIR/mnt_${lib_name}" mkdir -p "$tmp_mount" echo -e "${CYAN}Copying ${lib_name} runtime libraries into package...${RESET}" unsquashfs -f -d "$tmp_mount" "$mod_path" >/dev/null 2>&1 || { rm -rf "$tmp_mount"; return; } find "$tmp_mount/usr/lib64" -name "*.so*" \( -type f -o -type l \) 2>/dev/null | while read lib; do local rel_path="${lib#$tmp_mount}" local dest_dir="$PKG$(dirname "$rel_path")" mkdir -p "$dest_dir" cp -a "$lib" "$dest_dir/" 2>/dev/null done rm -rf "$tmp_mount" echo -e "${GREEN}${lib_name} libraries bundled into module.${RESET}" } inject_deps(){ local pkgs="potrace lensfun sqlite Imath" local tmp_mods="$TMPDIR/porteus_mods" mkdir -p "$tmp_mods" echo -e "\n${CYAN}Checking required host libraries...${RESET}" if ! pkg-config --exists pugixml 2>/dev/null && [ ! -f /usr/include/pugixml.hpp ]; then build_pugixml_module echo -e "${YELLOW}Activating pugixml module (build-time only)...${RESET}" if activate -q "$PUGIXML_MOD_PATH"; then MODULES_TO_DEACTIVATE+=("$PUGIXML_MOD_PATH") echo -e "${GREEN}pugixml module activated (will be statically linked).${RESET}" else die "Failed to activate pugixml module." fi else echo -e "${GREEN}pugixml already installed${RESET}" fi for pkg in $pkgs; do local found=0 case "$pkg" in potrace) [ -f /usr/include/potracelib.h ] && found=1 pkg-config --exists potrace 2>/dev/null && found=1 ;; lensfun) [ -f /usr/include/lensfun/lensfun.h ] && found=1 pkg-config --exists lensfun 2>/dev/null && found=1 ;; sqlite) [ -f /usr/include/sqlite3.h ] && found=1 pkg-config --exists sqlite3 2>/dev/null && found=1 ;; Imath) [ -f /usr/include/Imath/ImathVec.h ] && found=1 pkg-config --exists Imath 2>/dev/null && found=1 ;; esac if [ "$found" -eq 0 ]; then echo -e "${YELLOW}${pkg} not found in system. Downloading via getmod...${RESET}" if getmod -q "$pkg"; then local mod_file=$(find /tmp -name "${pkg}*.xzm" -type f 2>/dev/null | head -n1) if [ -n "$mod_file" ]; then local mod_basename=$(basename "$mod_file") mv "$mod_file" "$tmp_mods/$mod_basename" local mod_path="$tmp_mods/$mod_basename" echo -e "${YELLOW}Activating ${pkg} module...${RESET}" if activate -q "$mod_path"; then MODULES_TO_DEACTIVATE+=("$mod_path") INJECTED_LIBS+=("$pkg:$mod_path") echo -e "${GREEN}${pkg} module activated (will be bundled into final module).${RESET}" else die "Failed to activate ${pkg} module." fi else die "Failed to find downloaded module for ${pkg}." fi else die "Failed to download ${pkg} module via getmod." fi else echo -e "${GREEN}${pkg} already in system (no need to bundle).${RESET}" fi done echo -e "${GREEN}All required dependencies are present.${RESET}" } fix_desktop_icon(){ local desktop_file="$PKG/usr/share/applications/org.darktable.darktable.desktop" if [ -f "$desktop_file" ]; then sed -i 's|^Icon=darktable$|Icon=/usr/share/icons/hicolor/256x256/apps/darktable.png|' "$desktop_file" echo -e "${GREEN}Desktop file icon path fixed.${RESET}" fi } clean_pkg(){ echo -e "\n${CYAN}Cleaning package from unnecessary files...${RESET}" rm -f "$PKG/usr/lib64/libpugixml.a" rm -f "$PKG/usr/include/pugixml.hpp" rm -f "$PKG/usr/include/pugiconfig.hpp" rm -rf "$PKG/usr/lib64/cmake/pugixml" rm -rf "$PKG/usr/share/man" rm -rf "$PKG/usr/share/doc" rm -rf "$PKG/usr/share/metainfo" # Remove Python bindings for Imath (python version detected dynamically) local pyver=$(python3 -c 'import sys; print("%d.%d" % sys.version_info[:2])' 2>/dev/null) if [ -n "$pyver" ]; then rm -rf "$PKG/usr/lib64/python${pyver}" else rm -rf "$PKG"/usr/lib64/python3.* fi rm -f "$PKG/usr/lib64/libPyImath"* rm -f "$PKG/usr/share/darktable/pswp/LICENSE" rmdir "$PKG/usr/include" 2>/dev/null rmdir "$PKG/usr/lib64/cmake" 2>/dev/null echo -e "${GREEN}Package cleaned.${RESET}" } setup_locale(){ local ldir="$PKG/usr/share/locale" local lua_ldir="$PKG/usr/share/darktable/lua-scripts/locale" [ ! -d "$ldir" ] && return 0 echo -e "\n${CYAN}Darktable locale setup${RESET}" local locales=($(find "$ldir" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' 2>/dev/null | sort)) local count=${#locales[@]} [ "$count" -eq 0 ] && { echo -e "${RED}warn: no locales found${RESET}"; return 0; } echo -e "Available locales ($count):" local i=1 for loc in "${locales[@]}"; do printf " %2d) %-15s" "$i" "$loc" [ $((i % 5)) -eq 0 ] && echo i=$((i + 1)) done [ $((count % 5)) -ne 0 ] && echo local choice while true; do echo read -p "Locale number to keep (English always kept) [1-$count, Enter=English only]: " choice if [ -z "$choice" ]; then find "$ldir" -mindepth 1 -maxdepth 1 -type d -not -name "en*" -exec rm -rf {} + if [ -d "$lua_ldir" ]; then rm -rf "$lua_ldir" echo -e "${CYAN}info: lua-scripts locale directory removed completely${RESET}" fi echo -e "${GREEN}info: only English kept${RESET}" return 0 fi [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le "$count" ] && break echo -e "${RED}warn: invalid input, try again${RESET}" done local target="${locales[$((choice-1))]}" local base_lang="${target%%_*}" echo -e "${CYAN}Removing all locales except English and $target...${RESET}" find "$ldir" -mindepth 1 -maxdepth 1 -type d -not -name "en*" -not -name "$target" -exec rm -rf {} + if [ -d "$lua_ldir" ]; then local before_count=$(find "$lua_ldir" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l) find "$lua_ldir" -mindepth 1 -maxdepth 1 -type d -not -name "en*" -not -name "${base_lang}*" -exec rm -rf {} + local after_count=$(find "$lua_ldir" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l) if [ "$after_count" -eq 0 ]; then rm -rf "$lua_ldir" echo -e "${CYAN}info: no matching lua-scripts locales found, directory removed${RESET}" else local kept_locales=$(find "$lua_ldir" -mindepth 1 -maxdepth 1 -type d -printf '%f ' 2>/dev/null) echo -e "${CYAN}info: kept $after_count/$before_count lua-scripts locales: ${kept_locales}${RESET}" fi fi echo -e "${GREEN}info: kept: en + $target${RESET}" } [ -z "$VERSION" ] && die "Could not detect latest $PRGNAM version." [ -f "/usr/bin/darktable" ] && MYVER=$(basename /var/lib/pkgtools/packages/darktable* 2>/dev/null | cut -d'-' -f2) [ "$MYVER" == "$VERSION" ] && { echo -e "You have the latest ${GREEN}$MYVER${RESET} version of $PRGNAM"; exit 0; } read -p "$(echo -e Would you like to build $PRGNAM ${GREEN}$VERSION${RESET} xzm module? [y/n])" -n 1 -r -s && echo [[ $REPLY =~ ^[Yy]$ ]] || exit 0 rm -rf "$TMPDIR" && mkdir -p "$SRC" "$PKG/usr/bin" "$PKG/usr/share/applications" cd "$TMPDIR" inject_deps echo -e "\nDownloading ${CYAN}${BOLD}${PRGNAM}-${VERSION}${RESET}" if wget -q "$SOURCE" -O "$TMPDIR/$PRGNAM.tar.xz"; then tar -xf "$TMPDIR/$PRGNAM.tar.xz" -C "$SRC" --strip-components=1 || die "Failed to extract" rm -f "$TMPDIR/$PRGNAM.tar.xz" else git clone --depth 1 --recursive --shallow-submodules --branch "release-${VERSION}" https://github.com/darktable-org/darktable.git "$SRC" 2>/dev/null || { rm -rf "$SRC" git clone --depth 1 --recursive --branch "release-${VERSION}" https://github.com/darktable-org/darktable.git "$SRC" || die "git clone failed" } fi [ -f "$SRC/CMakeLists.txt" ] && [ -d "$SRC/src" ] || die "Source validation failed." cd "$SRC" && mkdir -p build && cd build CMAKE_OPTS=( -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -DUSE_OPENCL=ON -DTESTBUILD_OPENCL_PROGRAMS=OFF -DUSE_WEBP=ON -DUSE_OPENEXR=OFF -DUSE_JXL=OFF -DUSE_AVIF=OFF -DUSE_HEIF=OFF -DUSE_OPENJPEG=OFF -DUSE_ICU=ON -DUSE_CAMERA_SUPPORT=ON -DBUILD_PRINT=OFF -DUSE_MAP=OFF -DUSE_AI=OFF -DUSE_GMIC=OFF -DUSE_GRAPHICSMAGICK=OFF -DUSE_IMAGEMAGICK=OFF -DUSE_PORTMIDI=OFF -DUSE_COLORD=OFF -DDONT_USE_INTERNAL_LUA=OFF -DUSE_LUA=ON -DUSE_BUNDLED_PUGIXML=ON -DALLOW_DOWNLOADING_PUGIXML=ON -DUSE_LIBRAW=ON -DDONT_USE_INTERNAL_LIBRAW=OFF -DSOURCE_PACKAGE=OFF -DBINARY_PACKAGE_BUILD=ON -Wno-dev -DBUILD_TESTING=OFF -DBUILD_CMSTEST=OFF ) cmake -G Ninja "${CMAKE_OPTS[@]}" .. || die "CMake configuration failed." ninja -j"$JOBS" || die "Compilation failed." DESTDIR="$PKG" ninja install || die "Installation failed." [ -f "$PKG/usr/bin/darktable" ] || die "Build failed: darktable binary not found." setup_locale if [ ${#INJECTED_LIBS[@]} -gt 0 ]; then echo -e "\n${CYAN}Bundling injected runtime dependencies into module...${RESET}" for entry in "${INJECTED_LIBS[@]}"; do if [[ "$entry" == *":"* ]]; then lib_name="${entry%%:*}" mod_path="${entry#*:}" copy_libs_to_pkg "$lib_name" "$mod_path" fi done fi clean_pkg fix_desktop_icon find "$PKG" -type f \( -path '*/bin/*' -o -path '*/libexec/*' -o -name '*.so' -o -name '*.so.*' \) -print0 | xargs -0 -r -P "$JOBS" strip --strip-unneeded 2>/dev/null || true rm -rf "$SRC" cd "$PKG" mkdir -p "$PKG/var/lib/pkgtools/packages" { echo "PACKAGE NAME: $PRGNAM-$VERSION-$ARCH" cat << EOM PACKAGE DESCRIPTION: darktable: darktable (photography workflow application) darktable: darktable: darktable is an open source photography workflow darktable: application and raw developer. darktable: darktable: https://www.darktable.org/ darktable: FILE LIST: EOM find * | grep -v var } > "$PKG/var/lib/pkgtools/packages/$PRGNAM-$VERSION-$ARCH" mkdir -p "$OUTPUT" dir2xzm "$PKG" "$OUTPUT/$PRGNAM-$VERSION-$ARCH-$BUILD.xzm" [ -f "$OUTPUT/${PRGNAM}-${VERSION}-${ARCH}-${BUILD}.xzm" ] && \ echo -e "\n${BOLD}Your $PRGNAM module is at: ${GREEN}${BOLD}$OUTPUT/$PRGNAM-$VERSION-${ARCH}-${BUILD}.xzm${RESET}\n${BOLD}Please copy it to your modules folder to survive a reboot.${RESET}\n" || \ echo -e "\n${RED}${BOLD}Failed. Your $PRGNAM module is not built.${RESET}\n" cleanup exit 0