diff --git a/modules/dnf/dnf b/modules/dnf/dnf new file mode 100644 index 0000000..8cdba38 --- /dev/null +++ b/modules/dnf/dnf @@ -0,0 +1,300 @@ +#!/usr/bin/env bash + +# Tell build process to exit if there are any errors. +set -euo pipefail + +# Fail the build if dnf5 isn't installed +if ! rpm -q dnf5 &>/dev/null; then + echo "ERROR: Main dependency 'dnf5' is not installed. Install 'dnf5' before using this module to solve this error." + exit 1 +fi + +# Pull in repos +get_json_array REPOS 'try .["repos"][]' "${1}" +if [[ ${#REPOS[@]} -gt 0 ]]; then + echo "Adding repositories" + # Substitute %OS_VERSION% & remove newlines/whitespaces from all repo entries + for i in "${!REPOS[@]}"; do + repo="${REPOS[$i]}" + repo="${repo//%OS_VERSION%/${OS_VERSION}}" + REPOS[$i]="${repo//[$'\t\r\n ']}" + # Remove spaces/newlines for all repos other than COPR + if [[ "${repo}" != "COPR "* ]]; then + REPOS[$i]="${repo//[$'\t\r\n ']}" + else + REPOS[$i]="${repo}" + fi + done + # dnf config-manager & dnf copr don't support adding multiple repositories at once, hence why for/done loop is used + for repo in "${REPOS[@]}"; do + if [[ "${repo}" =~ ^https?:\/\/.* ]]; then + echo "Adding repository URL: '${repo}'" + dnf -y config-manager addrepo --from-repofile="${repo}" + elif [[ "${repo}" == *".repo" ]] && [[ -f "${CONFIG_DIRECTORY}/dnf/${repo}" ]]; then + echo "Adding repository file: '${repo##*/}'" + dnf -y config-manager addrepo --from-repofile="${CONFIG_DIRECTORY}/dnf/${repo}" + fi + done +fi + +get_json_array COPR_REPOS 'try .["copr"][]' "${1}" +if [[ ${#COPR_REPOS[@]} -gt 0 ]]; then + echo "Adding COPR repos: ${COPR_REPOS[*]}" + for repo in "${COPR_REPOS[@]}"; do + echo "Adding COPR repository: '${repo}'" + dnf -y copr enable "${repo}" + done +fi + +# Install RPM keys if they are provided +get_json_array KEYS 'try .["keys"][]' "${1}" +if [[ ${#KEYS[@]} -gt 0 ]]; then + echo "Adding keys" + for KEY in "${KEYS[@]}"; do + KEY="${KEY//%OS_VERSION%/${OS_VERSION}}" + rpm --import "${KEY//[$'\t\r\n ']}" + done +fi + +# Create symlinks to fix packages that create directories in /opt +get_json_array OPTFIX 'try .["optfix"][]' "${1}" +if [[ ${#OPTFIX[@]} -gt 0 ]]; then + echo "Creating symlinks to fix packages that install to /opt" + # Create symlink for /opt to /var/opt since it is not created in the image yet + mkdir -p "/var/opt" + ln -snf "/var/opt" "/opt" + # Create symlinks for each directory specified in recipe.yml + for OPTPKG in "${OPTFIX[@]}"; do + OPTPKG="${OPTPKG%\"}" + OPTPKG="${OPTPKG#\"}" + mkdir -p "/usr/lib/opt/${OPTPKG}" + ln -s "../../usr/lib/opt/${OPTPKG}" "/var/opt/${OPTPKG}" + echo "Created symlinks for ${OPTPKG}" + done +fi + +# Install & remove group packages +get_json_array GROUP_INSTALL 'try .["group-install"].["packages"][]' "${1}" +get_json_array GROUP_REMOVE 'try .["group-remove"].["packages"][]' "${1}" + +# Get if 'install-weak-dependencies' is provided for group-install +WEAK_DEPENDENCIES=$(echo "${1}" | jq -r 'try .["group-install"].["install-weak-dependencies"]') + +if [[ -z "${WEAK_DEPENDENCIES}" ]] || [[ "${WEAK_DEPENDENCIES}" == "null" ]] || [[ "${WEAK_DEPENDENCIES}" == "true" ]]; then + WEAK_DEPS_FLAG="--setopt=install_weak_deps=True" +elif [[ "${WEAK_DEPENDENCIES}" == "false" ]]; then + WEAK_DEPS_FLAG="--setopt=install_weak_deps=False" +fi + +# Get if 'skip-unavailable-packages' is provided for group-install +SKIP_UNAVAILABLE=$(echo "${1}" | jq -r 'try .["group-install"].["skip-unavailable-packages"]') + +if [[ -z "${SKIP_UNAVAILABLE}" ]] || [[ "${SKIP_UNAVAILABLE}" == "null" ]] || [[ "${SKIP_UNAVAILABLE}" == "false" ]]; then + SKIP_UNAVAILABLE_FLAG="" +elif [[ "${SKIP_UNAVAILABLE}" == "true" ]]; then + SKIP_UNAVAILABLE_FLAG="--skip-unavailable" +fi + +# Get if 'skip-broken-packages' is provided for group-install +SKIP_BROKEN=$(echo "${1}" | jq -r 'try .["group-install"].["skip-broken-packages"]') + +if [[ -z "${SKIP_BROKEN}" ]] || [[ "${SKIP_BROKEN}" == "null" ]] || [[ "${SKIP_BROKEN}" == "false" ]]; then + SKIP_BROKEN_FLAG="" +elif [[ "${SKIP_BROKEN}" == "true" ]]; then + SKIP_BROKEN_FLAG="--skip-broken" +fi + +# Get if 'allow-erasing-packages' is provided for group-install +ALLOW_ERASING=$(echo "${1}" | jq -r 'try .["group-install"].["allow-erasing-packages"]') + +if [[ -z "${ALLOW_ERASING}" ]] || [[ "${ALLOW_ERASING}" == "null" ]] || [[ "${ALLOW_ERASING}" == "false" ]]; then + ALLOW_ERASING_FLAG="" +elif [[ "${ALLOW_ERASING}" == "true" ]]; then + ALLOW_ERASING_FLAG="--allowerasing" +fi + +if [[ ${#GROUP_INSTALL[@]} -gt 0 && ${#GROUP_REMOVE[@]} -gt 0 ]]; then + echo "Removing & Installing RPM groups" + echo "Removing: ${GROUP_REMOVE[*]}" + echo "Installing: ${GROUP_INSTALL[*]}" + dnf -y group remove "${GROUP_REMOVE[@]}" + dnf -y ${WEAK_DEPS_FLAG} group install --refresh ${SKIP_UNAVAILABLE_FLAG} ${SKIP_BROKEN_FLAG} ${ALLOW_ERASING_FLAG} "${GROUP_INSTALL[@]}" +elif [[ ${#GROUP_INSTALL[@]} -gt 0 ]]; then + echo "Installing RPM groups" + echo "Installing: ${GROUP_INSTALL[*]}" + dnf -y ${WEAK_DEPS_FLAG} group install --refresh ${SKIP_UNAVAILABLE_FLAG} ${SKIP_BROKEN_FLAG} ${ALLOW_ERASING_FLAG} "${GROUP_INSTALL[@]}" +elif [[ ${#GROUP_REMOVE[@]} -gt 0 ]]; then + echo "Removing RPM groups" + echo "Removing: ${GROUP_REMOVE[*]}" + dnf -y group remove "${GROUP_REMOVE[@]}" +fi + +get_json_array INSTALL_PKGS 'try .["install"].["packages"][]' "${1}" +get_json_array REMOVE_PKGS 'try .["remove"].["packages"][]' "${1}" + +# Get if 'install-weak-dependencies' is provided for package install +WEAK_DEPENDENCIES=$(echo "${1}" | jq -r 'try .["install"].["install-weak-dependencies"]') + +if [[ -z "${WEAK_DEPENDENCIES}" ]] || [[ "${WEAK_DEPENDENCIES}" == "null" ]] || [[ "${WEAK_DEPENDENCIES}" == "true" ]]; then + WEAK_DEPS_FLAG="--setopt=install_weak_deps=True" +elif [[ "${WEAK_DEPENDENCIES}" == "false" ]]; then + WEAK_DEPS_FLAG="--setopt=install_weak_deps=False" +fi + +# Get if 'skip-unavailable-packages' is provided for package install +SKIP_UNAVAILABLE=$(echo "${1}" | jq -r 'try .["install"].["skip-unavailable-packages"]') + +if [[ -z "${SKIP_UNAVAILABLE}" ]] || [[ "${SKIP_UNAVAILABLE}" == "null" ]] || [[ "${SKIP_UNAVAILABLE}" == "false" ]]; then + SKIP_UNAVAILABLE_FLAG="" +elif [[ "${SKIP_UNAVAILABLE}" == "true" ]]; then + SKIP_UNAVAILABLE_FLAG="--skip-unavailable" +fi + +# Get if 'skip-broken-packages' is provided for package install +SKIP_BROKEN=$(echo "${1}" | jq -r 'try .["install"].["skip-broken-packages"]') + +if [[ -z "${SKIP_BROKEN}" ]] || [[ "${SKIP_BROKEN}" == "null" ]] || [[ "${SKIP_BROKEN}" == "false" ]]; then + SKIP_BROKEN_FLAG="" +elif [[ "${SKIP_BROKEN}" == "true" ]]; then + SKIP_BROKEN_FLAG="--skip-broken" +fi + +# Get if 'allow-erasing-packages' is provided for package install +ALLOW_ERASING=$(echo "${1}" | jq -r 'try .["install"].["allow-erasing-packages"]') + +if [[ -z "${ALLOW_ERASING}" ]] || [[ "${ALLOW_ERASING}" == "null" ]] || [[ "${ALLOW_ERASING}" == "false" ]]; then + ALLOW_ERASING_FLAG="" +elif [[ "${ALLOW_ERASING}" == "true" ]]; then + ALLOW_ERASING_FLAG="--allowerasing" +fi + +# Get if 'remove-unused-dependencies' is provided for package removal +REMOVE_UNUSED_DEPS=$(echo "${1}" | jq -r 'try .["remove"].["remove-unused-dependencies"]') + +if [[ -z "${REMOVE_UNUSED_DEPS}" ]] || [[ "${REMOVE_UNUSED_DEPS}" == "null" ]] || [[ "${REMOVE_UNUSED_DEPS}" == "true" ]]; then + REMOVE_UNUSED_DEPS_FLAG="" +elif [[ "${REMOVE_UNUSED_DEPS}" == "false" ]]; then + REMOVE_UNUSED_DEPS_FLAG="--no-autoremove" +fi + +CLASSIC_INSTALL=false +HTTPS_INSTALL=false +LOCAL_INSTALL=false + +# Sort classic, URL & local install packages +if [[ ${#INSTALL_PKGS[@]} -gt 0 ]]; then + for i in "${!INSTALL_PKGS[@]}"; do + PKG="${INSTALL_PKGS[$i]}" + if [[ "${PKG}" =~ ^https?:\/\/.* ]]; then + INSTALL_PKGS[$i]="${PKG//%OS_VERSION%/${OS_VERSION}}" + HTTPS_INSTALL=true + HTTPS_PKGS+=("${INSTALL_PKGS[$i]}") + elif [[ ! "${PKG}" =~ ^https?:\/\/.* ]] && [[ -f "${CONFIG_DIRECTORY}/dnf/${PKG}" ]]; then + LOCAL_INSTALL=true + LOCAL_PKGS+=("${CONFIG_DIRECTORY}/dnf/${PKG}") + else + CLASSIC_INSTALL=true + CLASSIC_PKGS+=("${PKG}") + fi + done +fi + +# Function to inform the user about which type of packages is he installing +echo_rpm_install() { + if ${CLASSIC_INSTALL}; then + echo "Installing: ${CLASSIC_PKGS[*]}" + fi + if ${HTTPS_INSTALL}; then + echo "Installing package(s) directly from URL: ${HTTPS_PKGS[*]}" + fi + if ${LOCAL_INSTALL}; then + echo "Installing local package(s): ${LOCAL_PKGS[*]}" + fi +} + +# Remove & install RPM packages +if [[ ${#INSTALL_PKGS[@]} -gt 0 && ${#REMOVE_PKGS[@]} -gt 0 ]]; then + echo "Removing & Installing RPMs" + echo "Removing: ${REMOVE_PKGS[*]}" + echo_rpm_install + dnf -y remove ${REMOVE_UNUSED_DEPS_FLAG} "${REMOVE_PKGS[@]}" + dnf -y ${WEAK_DEPS_FLAG} install --refresh ${SKIP_UNAVAILABLE_FLAG} ${SKIP_BROKEN_FLAG} ${ALLOW_ERASING_FLAG} "${INSTALL_PKGS[@]}" +elif [[ ${#INSTALL_PKGS[@]} -gt 0 ]]; then + echo "Installing RPMs" + echo_rpm_install + dnf -y ${WEAK_DEPS_FLAG} install --refresh ${SKIP_UNAVAILABLE_FLAG} ${SKIP_BROKEN_FLAG} ${ALLOW_ERASING_FLAG} "${INSTALL_PKGS[@]}" +elif [[ ${#REMOVE_PKGS[@]} -gt 0 ]]; then + echo "Removing RPMs" + echo "Removing: ${REMOVE_PKGS[*]}" + dnf -y remove ${REMOVE_UNUSED_DEPS_FLAG} "${REMOVE_PKGS[@]}" +fi + +get_json_array REPLACE 'try .["replace"][]' "$1" + +# Replace RPM packages from any repository +if [[ ${#REPLACE[@]} -gt 0 ]]; then + for REPLACEMENT in "${REPLACE[@]}"; do + + # Get repository + REPO=$(echo "${REPLACEMENT}" | jq -r 'try .["from-repo"]') + REPO="${REPO//%OS_VERSION%/${OS_VERSION}}" + REPO="${REPO//[$'\t\r\n ']}" + + # Ensure repository is provided + if [[ "${REPO}" == "null" ]] || [[ -z "${REPO}" ]]; then + echo "ERROR: Key 'from-repo' was declared, but repository URL was not provided." + exit 1 + fi + + # Get packages to replace + get_json_array PACKAGES 'try .["packages"][]' "${REPLACEMENT}" + + # Ensure packages are provided + if [[ ${#PACKAGES[@]} -eq 0 ]]; then + echo "ERROR: No packages were provided for repository '${REPO}'." + exit 1 + fi + + # Get if 'install-weak-dependencies' is provided for package replace + WEAK_DEPENDENCIES=$(echo "${REPLACEMENT}" | jq -r 'try .["install-weak-dependencies"]') + + if [[ -z "${WEAK_DEPENDENCIES}" ]] || [[ "${WEAK_DEPENDENCIES}" == "null" ]] || [[ "${WEAK_DEPENDENCIES}" == "true" ]]; then + WEAK_DEPS_FLAG="--setopt=install_weak_deps=True" + elif [[ "${WEAK_DEPENDENCIES}" == "false" ]]; then + WEAK_DEPS_FLAG="--setopt=install_weak_deps=False" + fi + + # Get if 'skip-unavailable-packages' is provided for package replace + SKIP_UNAVAILABLE=$(echo "${REPLACEMENT}" | jq -r 'try .["skip-unavailable-packages"]') + + if [[ -z "${SKIP_UNAVAILABLE}" ]] || [[ "${SKIP_UNAVAILABLE}" == "null" ]] || [[ "${SKIP_UNAVAILABLE}" == "false" ]]; then + SKIP_UNAVAILABLE_FLAG="" + elif [[ "${SKIP_UNAVAILABLE}" == "true" ]]; then + SKIP_UNAVAILABLE_FLAG="--skip-unavailable" + fi + + # Get if 'skip-broken-packages' is provided for package replace + SKIP_BROKEN=$(echo "${REPLACEMENT}" | jq -r 'try .["skip-broken-packages"]') + + if [[ -z "${SKIP_BROKEN}" ]] || [[ "${SKIP_BROKEN}" == "null" ]] || [[ "${SKIP_BROKEN}" == "false" ]]; then + SKIP_BROKEN_FLAG="" + elif [[ "${SKIP_BROKEN}" == "true" ]]; then + SKIP_BROKEN_FLAG="--skip-broken" + fi + + # Get if 'allow-erasing-packages' is provided for package replace + ALLOW_ERASING=$(echo "${REPLACEMENT}" | jq -r 'try .["allow-erasing-packages"]') + + if [[ -z "${ALLOW_ERASING}" ]] || [[ "${ALLOW_ERASING}" == "null" ]] || [[ "${ALLOW_ERASING}" == "false" ]]; then + ALLOW_ERASING_FLAG="" + elif [[ "${ALLOW_ERASING}" == "true" ]]; then + ALLOW_ERASING_FLAG="--allowerasing" + fi + + echo "Replacing packages from repository: '${REPO}'" + echo "Replacing: ${PACKAGES[*]}" + + dnf -y ${WEAK_DEPS_FLAG} distro-sync --refresh ${SKIP_UNAVAILABLE_FLAG} ${SKIP_BROKEN_FLAG} ${ALLOW_ERASING_FLAG} --repo "${REPO}" "${PACKAGES[@]}" + + done +fi diff --git a/recipes/common/jp-packages-bootc.yml b/recipes/common/jp-packages-bootc.yml index 6f1103e..0f76a9e 100644 --- a/recipes/common/jp-packages-bootc.yml +++ b/recipes/common/jp-packages-bootc.yml @@ -28,6 +28,7 @@ modules: - cabextract - xorg-x11-font-utils - fontconfig + - rust-nu - https://downloads.sourceforge.net/project/mscorefonts2/rpms/msttcore-fonts-installer-2.6-1.noarch.rpm - type: systemd user: diff --git a/recipes/common/jp-packages.yml b/recipes/common/jp-packages.yml index 9bbbbab..baf28df 100644 --- a/recipes/common/jp-packages.yml +++ b/recipes/common/jp-packages.yml @@ -25,6 +25,7 @@ modules: - brave-browser - syncthing - fish + - rust-nu - type: systemd user: enabled: