#!/bin/bash # SPDX-License-Identifier: LGPL-2.1-or-later # Build a .deb package from the installed build directory # # This script creates a Debian binary package following Debian Policy Manual guidelines. # See: https://www.debian.org/doc/debian-policy/ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" # Parse arguments INSTALL_DIR="${1:-}" OUTPUT_DIR="${2:-}" VERSION="${3:-}" if [ -z "$INSTALL_DIR" ] || [ -z "$OUTPUT_DIR" ]; then echo "Usage: $0 [version]" echo " install-dir: Directory containing installed build (e.g., build/release/install)" echo " output-dir: Directory to write the .deb file" echo " version: Package version (default: from git describe)" exit 1 fi # Validate install directory exists if [ ! -d "$INSTALL_DIR" ]; then echo "Error: Install directory does not exist: $INSTALL_DIR" exit 1 fi # Get version from git if not provided if [ -z "$VERSION" ]; then VERSION=$(cd "$PROJECT_ROOT" && git describe --tags --always 2>/dev/null || echo "0.1.0") fi # Convert version to Debian-compatible format # Debian Policy Manual 5.6.12: version must start with a digit # Format: [epoch:]upstream_version[-debian_revision] convert_to_debian_version() { local ver="$1" # Remove leading 'v' if present (common in git tags) ver="${ver#v}" # Replace hyphens with dots (hyphens have special meaning in Debian versions) ver="${ver//-/.}" # If version starts with a letter (e.g., 'weekly'), prefix with '0~' # The ~ character sorts before anything else, ensuring: # 0~weekly.2025.01.01 < 0.1.0 < 1.0.0 if [[ "$ver" =~ ^[a-zA-Z] ]]; then ver="0~${ver}" fi echo "$ver" } VERSION=$(convert_to_debian_version "$VERSION") PACKAGE_NAME="kindred-create" ARCH="amd64" # Debian package naming: __.deb DEB_NAME="${PACKAGE_NAME}_${VERSION}_${ARCH}" echo "========================================" echo "Building Debian package" echo "========================================" echo " Package: ${PACKAGE_NAME}" echo " Version: ${VERSION}" echo " Arch: ${ARCH}" echo " Source: ${INSTALL_DIR}" echo " Output: ${OUTPUT_DIR}/${DEB_NAME}.deb" echo "========================================" # Create staging directory STAGING_DIR=$(mktemp -d) trap "rm -rf ${STAGING_DIR}" EXIT # Create debian package structure following Filesystem Hierarchy Standard mkdir -p "${STAGING_DIR}/DEBIAN" mkdir -p "${STAGING_DIR}/opt/${PACKAGE_NAME}" mkdir -p "${STAGING_DIR}/usr/bin" mkdir -p "${STAGING_DIR}/usr/share/applications" mkdir -p "${STAGING_DIR}/usr/share/icons/hicolor/scalable/apps" mkdir -p "${STAGING_DIR}/usr/share/icons/hicolor/48x48/apps" mkdir -p "${STAGING_DIR}/usr/share/icons/hicolor/256x256/apps" mkdir -p "${STAGING_DIR}/usr/share/mime/packages" mkdir -p "${STAGING_DIR}/usr/share/metainfo" mkdir -p "${STAGING_DIR}/usr/share/doc/${PACKAGE_NAME}" # Copy installed files echo "Copying installed files..." cp -a "${INSTALL_DIR}"/* "${STAGING_DIR}/opt/${PACKAGE_NAME}/" # Create wrapper scripts in /usr/bin that set up the environment # The binaries need LD_LIBRARY_PATH to find bundled libraries cat > "${STAGING_DIR}/usr/bin/kindred-create" << 'WRAPPER' #!/bin/bash export KINDRED_CREATE_HOME="/opt/kindred-create" export LD_LIBRARY_PATH="${KINDRED_CREATE_HOME}/lib:${LD_LIBRARY_PATH:-}" export QT_PLUGIN_PATH="${KINDRED_CREATE_HOME}/lib/qt6/plugins:${QT_PLUGIN_PATH:-}" export QT_QPA_PLATFORM_PLUGIN_PATH="${KINDRED_CREATE_HOME}/lib/qt6/plugins/platforms:${QT_QPA_PLATFORM_PLUGIN_PATH:-}" export PYTHONHOME="${KINDRED_CREATE_HOME}" export PYTHONPATH="${KINDRED_CREATE_HOME}/lib/python3.11:${KINDRED_CREATE_HOME}/lib/python3.11/site-packages:${PYTHONPATH:-}" export XDG_DATA_DIRS="${KINDRED_CREATE_HOME}/share:${XDG_DATA_DIRS:-/usr/share}" export GI_TYPELIB_PATH="${KINDRED_CREATE_HOME}/lib/girepository-1.0:${GI_TYPELIB_PATH:-}" # XKB keyboard configuration - use bundled data to avoid hardcoded CI paths in libxkbcommon export XKB_CONFIG_ROOT="${KINDRED_CREATE_HOME}/share/X11/xkb" # Fontconfig - use bundled configuration to avoid missing default config export FONTCONFIG_FILE="${KINDRED_CREATE_HOME}/etc/fonts/fonts.conf" export FONTCONFIG_PATH="${KINDRED_CREATE_HOME}/etc/fonts" # Qt Wayland fractional scaling — force integer rounding to avoid blurry text export QT_SCALE_FACTOR_ROUNDING_POLICY=RoundPreferFloor export QT_ENABLE_HIGHDPI_SCALING=1 # Use system CA certificates so bundled Python trusts internal CAs (e.g. FreeIPA) # The bundled openssl has a hardcoded cafile from the build environment which # does not exist on the target system. if [ -z "${SSL_CERT_FILE:-}" ]; then for ca in /etc/ssl/certs/ca-certificates.crt /etc/pki/tls/certs/ca-bundle.crt; do if [ -f "$ca" ]; then export SSL_CERT_FILE="$ca" break fi done fi # Try different binary names (FreeCAD or freecad depending on build) if [ -x "${KINDRED_CREATE_HOME}/bin/FreeCAD" ]; then exec "${KINDRED_CREATE_HOME}/bin/FreeCAD" "$@" elif [ -x "${KINDRED_CREATE_HOME}/bin/freecad" ]; then exec "${KINDRED_CREATE_HOME}/bin/freecad" "$@" else echo "Error: Cannot find FreeCAD binary in ${KINDRED_CREATE_HOME}/bin/" >&2 exit 1 fi WRAPPER chmod 755 "${STAGING_DIR}/usr/bin/kindred-create" cat > "${STAGING_DIR}/usr/bin/kindred-create-cmd" << 'WRAPPER' #!/bin/bash export KINDRED_CREATE_HOME="/opt/kindred-create" export LD_LIBRARY_PATH="${KINDRED_CREATE_HOME}/lib:${LD_LIBRARY_PATH:-}" export PYTHONHOME="${KINDRED_CREATE_HOME}" export PYTHONPATH="${KINDRED_CREATE_HOME}/lib/python3.11:${KINDRED_CREATE_HOME}/lib/python3.11/site-packages:${PYTHONPATH:-}" # Use system CA certificates (see kindred-create wrapper for details) if [ -z "${SSL_CERT_FILE:-}" ]; then for ca in /etc/ssl/certs/ca-certificates.crt /etc/pki/tls/certs/ca-bundle.crt; do if [ -f "$ca" ]; then export SSL_CERT_FILE="$ca" break fi done fi # Try different binary names (FreeCADCmd or freecadcmd depending on build) if [ -x "${KINDRED_CREATE_HOME}/bin/FreeCADCmd" ]; then exec "${KINDRED_CREATE_HOME}/bin/FreeCADCmd" "$@" elif [ -x "${KINDRED_CREATE_HOME}/bin/freecadcmd" ]; then exec "${KINDRED_CREATE_HOME}/bin/freecadcmd" "$@" else echo "Error: Cannot find FreeCADCmd binary in ${KINDRED_CREATE_HOME}/bin/" >&2 exit 1 fi WRAPPER chmod 755 "${STAGING_DIR}/usr/bin/kindred-create-cmd" # Create desktop entry following freedesktop.org specification cat > "${STAGING_DIR}/usr/share/applications/kindred-create.desktop" << 'EOF' [Desktop Entry] Version=1.1 Type=Application Name=Kindred Create GenericName=CAD Application Comment=Engineering-focused parametric 3D CAD platform Exec=/usr/bin/kindred-create %F Icon=kindred-create Terminal=false Categories=Graphics;Science;Engineering; MimeType=application/x-extension-fcstd;application/x-freecad; Keywords=CAD;3D;modeling;engineering;design;parametric;FreeCAD; StartupNotify=true StartupWMClass=FreeCAD EOF # Copy Kindred branding icons (prefer Kindred logo over FreeCAD default) if [ -f "${PROJECT_ROOT}/resources/branding/kindred-logo.svg" ]; then cp "${PROJECT_ROOT}/resources/branding/kindred-logo.svg" \ "${STAGING_DIR}/usr/share/icons/hicolor/scalable/apps/kindred-create.svg" elif [ -f "${INSTALL_DIR}/share/icons/hicolor/scalable/apps/org.freecad.FreeCAD.svg" ]; then cp "${INSTALL_DIR}/share/icons/hicolor/scalable/apps/org.freecad.FreeCAD.svg" \ "${STAGING_DIR}/usr/share/icons/hicolor/scalable/apps/kindred-create.svg" fi # Generate PNG icons from the Kindred SVG for better desktop integration if command -v rsvg-convert > /dev/null 2>&1 && \ [ -f "${STAGING_DIR}/usr/share/icons/hicolor/scalable/apps/kindred-create.svg" ]; then for size in 48 256; do rsvg-convert -w "$size" -h "$size" \ "${STAGING_DIR}/usr/share/icons/hicolor/scalable/apps/kindred-create.svg" \ -o "${STAGING_DIR}/usr/share/icons/hicolor/${size}x${size}/apps/kindred-create.png" \ 2>/dev/null || true done else # Fallback: copy FreeCAD PNGs if available for size in 48 256; do if [ -f "${INSTALL_DIR}/share/icons/hicolor/${size}x${size}/apps/org.freecad.FreeCAD.png" ]; then cp "${INSTALL_DIR}/share/icons/hicolor/${size}x${size}/apps/org.freecad.FreeCAD.png" \ "${STAGING_DIR}/usr/share/icons/hicolor/${size}x${size}/apps/kindred-create.png" fi done fi # Create MIME type definition for .fcstd files cat > "${STAGING_DIR}/usr/share/mime/packages/kindred-create.xml" << 'EOF' FreeCAD Document EOF # Create AppStream metainfo for software centers cat > "${STAGING_DIR}/usr/share/metainfo/kindred-create.metainfo.xml" << EOF kindred-create Kindred Create Engineering-focused parametric 3D CAD platform CC0-1.0 LGPL-2.1-or-later

Kindred Create is an engineering-focused parametric 3D CAD platform built on FreeCAD 1.0+. It provides a streamlined interface for mechanical engineering and product design.

Features include:

  • Parametric modeling with constraint-based sketcher
  • Part Design workbench for solid modeling
  • Assembly workbench for multi-part designs
  • TechDraw workbench for 2D technical drawings
  • FEM workbench for finite element analysis
  • CAM workbench for CNC machining
kindred-create.desktop https://gitea.kindred.internal/kindred/create https://gitea.kindred.internal/kindred/create/issues kindred-create kindred-create-cmd
EOF # Create basic copyright file (required by Debian policy) cat > "${STAGING_DIR}/usr/share/doc/${PACKAGE_NAME}/copyright" << 'EOF' Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: Kindred Create Upstream-Contact: Kindred Team Source: https://gitea.kindred.internal/kindred/create Files: * Copyright: 2024-2026 Kindred 2001-2024 FreeCAD Contributors License: LGPL-2.1-or-later License: LGPL-2.1-or-later This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. . On Debian systems, the complete text of the GNU Lesser General Public License can be found in `/usr/share/common-licenses/LGPL-2.1'. EOF # Generate control file with actual version sed "s/\${VERSION}/${VERSION}/" "${SCRIPT_DIR}/control" > "${STAGING_DIR}/DEBIAN/control" # Calculate and append installed size (in KiB, as required by Debian policy) INSTALLED_SIZE=$(du -sk "${STAGING_DIR}" | cut -f1) echo "Installed-Size: ${INSTALLED_SIZE}" >> "${STAGING_DIR}/DEBIAN/control" # Create postinst script for post-installation setup cat > "${STAGING_DIR}/DEBIAN/postinst" << 'EOF' #!/bin/sh set -e case "$1" in configure) # Update desktop database if command -v update-desktop-database > /dev/null 2>&1; then update-desktop-database -q /usr/share/applications 2>/dev/null || true fi # Update icon cache if command -v gtk-update-icon-cache > /dev/null 2>&1; then gtk-update-icon-cache -q -t -f /usr/share/icons/hicolor 2>/dev/null || true fi # Update MIME database if command -v update-mime-database > /dev/null 2>&1; then update-mime-database /usr/share/mime 2>/dev/null || true fi ;; esac exit 0 EOF chmod 755 "${STAGING_DIR}/DEBIAN/postinst" # Create postrm script for post-removal cleanup cat > "${STAGING_DIR}/DEBIAN/postrm" << 'EOF' #!/bin/sh set -e case "$1" in remove|purge) # Update desktop database if command -v update-desktop-database > /dev/null 2>&1; then update-desktop-database -q /usr/share/applications 2>/dev/null || true fi # Update icon cache if command -v gtk-update-icon-cache > /dev/null 2>&1; then gtk-update-icon-cache -q -t -f /usr/share/icons/hicolor 2>/dev/null || true fi # Update MIME database if command -v update-mime-database > /dev/null 2>&1; then update-mime-database /usr/share/mime 2>/dev/null || true fi ;; esac exit 0 EOF chmod 755 "${STAGING_DIR}/DEBIAN/postrm" # Set proper permissions following Debian policy # Directories: 755, Files: 644, Executables: 755 echo "Setting file permissions..." find "${STAGING_DIR}" -type d -exec chmod 755 {} \; find "${STAGING_DIR}/opt" -type f -exec chmod 644 {} \; # Make binaries executable if [ -d "${STAGING_DIR}/opt/${PACKAGE_NAME}/bin" ]; then find "${STAGING_DIR}/opt/${PACKAGE_NAME}/bin" -type f -exec chmod 755 {} \; fi # Make shared libraries executable (required for proper loading) find "${STAGING_DIR}/opt/${PACKAGE_NAME}" -name "*.so" -type f -exec chmod 755 {} \; find "${STAGING_DIR}/opt/${PACKAGE_NAME}" -name "*.so.*" -type f -exec chmod 755 {} \; # Ensure DEBIAN scripts have correct permissions (must be 0555 to 0775) chmod 755 "${STAGING_DIR}/DEBIAN/postinst" chmod 755 "${STAGING_DIR}/DEBIAN/postrm" # Build the .deb package # --root-owner-group ensures all files are owned by root:root (standard practice) # -Zxz with -z1 uses fast compression (large packages can take very long with default settings) mkdir -p "${OUTPUT_DIR}" echo "Building package (this may take a few minutes for large packages)..." echo "Staging directory size: $(du -sh "${STAGING_DIR}" | cut -f1)" dpkg-deb -Zxz -z1 --build --root-owner-group "${STAGING_DIR}" "${OUTPUT_DIR}/${DEB_NAME}.deb" # Verify the package echo "Verifying package..." dpkg-deb --info "${OUTPUT_DIR}/${DEB_NAME}.deb" # Generate checksums cd "${OUTPUT_DIR}" sha256sum "${DEB_NAME}.deb" > "${DEB_NAME}.deb.sha256" echo "" echo "========================================" echo "Package built successfully!" echo "========================================" echo " File: ${OUTPUT_DIR}/${DEB_NAME}.deb" echo " Size: $(ls -lh "${DEB_NAME}.deb" | awk '{print $5}')" echo " SHA256: $(cat "${DEB_NAME}.deb.sha256" | cut -d' ' -f1)" echo "========================================" echo "" echo "Install with: sudo dpkg -i ${DEB_NAME}.deb" echo " sudo apt-get install -f # to resolve dependencies"