particle-os-modules/modules/gnome-extensions/gnome-extensions.sh
2024-09-17 17:30:33 -04:00

305 lines
16 KiB
Bash

#!/usr/bin/env bash
# shellcheck disable=SC2128,SC2178
# Tell build process to exit if there are any errors.
set -euo pipefail
get_yaml_array INSTALL '.install[]' "$1"
get_yaml_array UNINSTALL '.uninstall[]' "$1"
if [[ ${#INSTALL[@]} -lt 1 ]] && [[ ${#UNINSTALL[@]} -lt 1 ]]; then
echo "ERROR: You did not specify the extension to install or uninstall in module recipe file"
exit 1
fi
if ! command -v gnome-shell &> /dev/null; then
echo "ERROR: Your custom image is using non-Gnome desktop environment, where Gnome extensions are not supported"
exit 1
fi
echo "Testing connection with https://extensions.gnome.org/..."
if ! curl --output /dev/null --silent --head --fail "https://extensions.gnome.org/"; then
echo "ERROR: Connection unsuccessful."
echo " This usually happens when https://extensions.gnome.org/ website is down."
echo " Please try again later (or disable the module temporarily)"
exit 1
else
echo "Connection successful, proceeding."
fi
GNOME_VER=$(gnome-shell --version | sed 's/[^0-9]*\([0-9]*\).*/\1/')
echo "Gnome version: ${GNOME_VER}"
LEGACY=false
# Legacy support for installing extensions, to retain compatibility with legacy configs
if [[ ${#INSTALL[@]} -gt 0 ]]; then
for EXTENSION in "${INSTALL[@]}"; do
# If extension contains .v12 suffix at the end, than it's the legacy install entry
# 12 number in .v12 is just an example, any integer after it is allowed
shopt -s extglob
if [[ ! "${EXTENSION}" == *".v"*([0-9]) ]]; then
break
else
LEGACY=true
fi
shopt -u extglob
echo "ATTENTION: This is the legacy method of installing extensions."
echo " Change the install entry to literal name of the extension"
echo " Please see the latest docs of gnome-extensions module for more details:"
echo " https://blue-build.org/reference/modules/gnome-extensions/"
URL="https://extensions.gnome.org/extension-data/${EXTENSION}.shell-extension.zip"
TMP_DIR="/tmp/${EXTENSION}"
ARCHIVE=$(basename "${URL}")
ARCHIVE_DIR="${TMP_DIR}/${ARCHIVE}"
VERSION=$(echo "${EXTENSION}" | grep -oP 'v\d+')
echo "Installing ${EXTENSION} Gnome extension with version ${VERSION}"
# Download archive
wget --directory-prefix="${TMP_DIR}" "${URL}"
# Extract archive
echo "Extracting ZIP archive"
unzip "${ARCHIVE_DIR}" -d "${TMP_DIR}" > /dev/null
# Remove archive
echo "Removing archive"
rm "${ARCHIVE_DIR}"
# Read necessary info from metadata.json
echo "Reading necessary info from metadata.json"
EXTENSION_NAME=$(jq -r '.["name"]' < "${TMP_DIR}/metadata.json")
UUID=$(jq -r '.["uuid"]' < "${TMP_DIR}/metadata.json")
EXT_GNOME_VER=$(jq -r '.["shell-version"][]' < "${TMP_DIR}/metadata.json")
# If extension does not have the important key in metadata.json,
# inform the user & fail the build
if [[ "${UUID}" == "null" ]]; then
echo "ERROR: Extension '${EXTENSION_NAME}' doesn't have 'uuid' key inside metadata.json"
echo "You may inform the extension developer about this error, as he can fix it"
exit 1
fi
if [[ "${EXT_GNOME_VER}" == "null" ]]; then
echo "ERROR: Extension '${EXTENSION_NAME}' doesn't have 'shell-version' key inside metadata.json"
echo "You may inform the extension developer about this error, as he can fix it"
exit 1
fi
# Compare if extension is compatible with current Gnome version
# If extension is not compatible, inform the user & fail the build
if ! [[ "${EXT_GNOME_VER}" =~ "${GNOME_VER}" ]]; then
echo "ERROR: Extension '${EXTENSION_NAME}' is not compatible with current Gnome v${GNOME_VER}!"
exit 1
fi
# Install main extension files
echo "Installing main extension files"
install -d -m 0755 "/usr/share/gnome-shell/extensions/${UUID}/"
find "${TMP_DIR}" -mindepth 1 -maxdepth 1 ! -path "*locale*" ! -path "*schemas*" -exec cp -r {} "/usr/share/gnome-shell/extensions/${UUID}/" \;
find "/usr/share/gnome-shell/extensions/${UUID}" -type d -exec chmod 0755 {} +
find "/usr/share/gnome-shell/extensions/${UUID}" -type f -exec chmod 0644 {} +
# Install schema
if [[ -d "${TMP_DIR}/schemas" ]]; then
echo "Installing schema extension file"
# Workaround for extensions, which explicitly require compiled schema to be in extension UUID directory (rare scenario due to how extension is programmed in non-standard way)
# Error code example:
# GLib.FileError: Failed to open file “/usr/share/gnome-shell/extensions/flypie@schneegans.github.com/schemas/gschemas.compiled”: open() failed: No such file or directory
# If any extension produces this error, it can be added in if statement below to solve the problem
# Fly-Pie or PaperWM
if [[ "${UUID}" == "flypie@schneegans.github.com" || "${UUID}" == "paperwm@paperwm.github.com" ]]; then
install -d -m 0755 "/usr/share/gnome-shell/extensions/${UUID}/schemas/"
install -D -p -m 0644 "${TMP_DIR}/schemas/"*.gschema.xml "/usr/share/gnome-shell/extensions/${UUID}/schemas/"
glib-compile-schemas "/usr/share/gnome-shell/extensions/${UUID}/schemas/" &>/dev/null
else
# Regular schema installation
install -d -m 0755 "/usr/share/glib-2.0/schemas/"
install -D -p -m 0644 "${TMP_DIR}/schemas/"*.gschema.xml "/usr/share/glib-2.0/schemas/"
fi
fi
# Install languages
# Locale is not crucial for extensions to work, as they will fallback to gschema.xml
# Some of them might not have any locale at the moment
# So that's why I made a check for directory
if [[ -d "${TMP_DIR}/locale" ]]; then
echo "Installing language extension files"
install -d -m 0755 "/usr/share/locale/"
cp -r "${TMP_DIR}/locale"/* "/usr/share/locale/"
fi
# Delete the temporary directory
echo "Cleaning up the temporary directory"
rm -r "${TMP_DIR}"
echo "Extension '${EXTENSION_NAME}' is successfully installed"
echo "----------------------------------INSTALLATION DONE----------------------------------"
done
fi
# New method of installing extensions
if [[ ${#INSTALL[@]} -gt 0 ]] && ! "${LEGACY}"; then
for INSTALL_EXT in "${INSTALL[@]}"; do
if [[ ! "${INSTALL_EXT}" =~ ^[0-9]+$ ]]; then
# Literal-name extension config
# Replaces whitespaces with %20 for install entries which contain extension name, since URLs can't contain whitespace
WHITESPACE_HTML="${INSTALL_EXT// /%20}"
URL_QUERY=$(curl -sf "https://extensions.gnome.org/extension-query/?search=${WHITESPACE_HTML}")
QUERIED_EXT=$(echo "${URL_QUERY}" | jq ".extensions[] | select(.name == \"${INSTALL_EXT}\")")
if [[ -z "${QUERIED_EXT}" ]] || [[ "${QUERIED_EXT}" == "null" ]]; then
echo "ERROR: Extension '${INSTALL_EXT}' does not exist in https://extensions.gnome.org/ website"
echo " Extension name is case-sensitive, so be sure that you typed it correctly,"
echo " including the correct uppercase & lowercase characters"
exit 1
fi
readarray -t EXT_UUID < <(echo "${QUERIED_EXT}" | jq -r '.["uuid"]')
readarray -t EXT_NAME < <(echo "${QUERIED_EXT}" | jq -r '.["name"]')
if [[ ${#EXT_UUID[@]} -gt 1 ]] || [[ ${#EXT_NAME[@]} -gt 1 ]]; then
echo "ERROR: Multiple compatible Gnome extensions with the same name are found, which this module cannot select"
echo " To solve this problem, please use PK ID as a module input entry instead of the extension name"
echo " You can get PK ID from the extension URL, like from Blur my Shell's 3193 PK ID example below:"
echo " https://extensions.gnome.org/extension/3193/blur-my-shell/"
exit 1
fi
# Gets suitable extension version for Gnome version from the image
SUITABLE_VERSION=$(echo "${QUERIED_EXT}" | jq ".shell_version_map[\"${GNOME_VER}\"].version")
if [[ -z "${SUITABLE_VERSION}" ]] || [[ "${SUITABLE_VERSION}" == "null" ]]; then
echo "ERROR: Extension '${EXT_NAME}' is not compatible with Gnome v${GNOME_VER} in your image"
exit 1
fi
else
# PK ID extension config fallback if specified
URL_QUERY=$(curl -sf "https://extensions.gnome.org/extension-info/?pk=${INSTALL_EXT}")
PK_EXT=$(echo "${URL_QUERY}" | jq -r '.["pk"]' 2>/dev/null)
if [[ -z "${PK_EXT}" ]] || [[ "${PK_EXT}" == "null" ]]; then
echo "ERROR: Extension with PK ID '${INSTALL_EXT}' does not exist in https://extensions.gnome.org/ website"
echo " Please assure that you typed the PK ID correctly,"
echo " and that it exists in Gnome extensions website"
exit 1
fi
EXT_UUID=$(echo "${URL_QUERY}" | jq -r '.["uuid"]')
EXT_NAME=$(echo "${URL_QUERY}" | jq -r '.["name"]')
SUITABLE_VERSION=$(echo "${URL_QUERY}" | jq ".shell_version_map[\"${GNOME_VER}\"].version")
# Fail the build if extension is not compatible with the current Gnome version
if [[ -z "${SUITABLE_VERSION}" ]] || [[ "${SUITABLE_VERSION}" == "null" ]]; then
echo "ERROR: Extension '${EXT_NAME}' is not compatible with Gnome v${GNOME_VER} in your image"
exit 1
fi
fi
# Removes every @ symbol from UUID, since extension URL doesn't contain @ symbol
URL="https://extensions.gnome.org/extension-data/${EXT_UUID//@/}.v${SUITABLE_VERSION}.shell-extension.zip"
TMP_DIR="/tmp/${EXT_UUID}"
ARCHIVE=$(basename "${URL}")
ARCHIVE_DIR="${TMP_DIR}/${ARCHIVE}"
echo "Installing '${EXT_NAME}' Gnome extension with version ${SUITABLE_VERSION}"
# Download archive
wget --directory-prefix="${TMP_DIR}" "${URL}"
# Extract archive
echo "Extracting ZIP archive"
unzip "${ARCHIVE_DIR}" -d "${TMP_DIR}" > /dev/null
# Remove archive
echo "Removing archive"
rm "${ARCHIVE_DIR}"
# Install main extension files
echo "Installing main extension files"
install -d -m 0755 "/usr/share/gnome-shell/extensions/${EXT_UUID}/"
find "${TMP_DIR}" -mindepth 1 -maxdepth 1 ! -path "*locale*" ! -path "*schemas*" -exec cp -r {} "/usr/share/gnome-shell/extensions/${EXT_UUID}/" \;
find "/usr/share/gnome-shell/extensions/${EXT_UUID}" -type d -exec chmod 0755 {} +
find "/usr/share/gnome-shell/extensions/${EXT_UUID}" -type f -exec chmod 0644 {} +
# Install schema
if [[ -d "${TMP_DIR}/schemas" ]]; then
echo "Installing schema extension file"
# Workaround for extensions, which explicitly require compiled schema to be in extension UUID directory (rare scenario due to how extension is programmed in non-standard way)
# Error code example:
# GLib.FileError: Failed to open file “/usr/share/gnome-shell/extensions/flypie@schneegans.github.com/schemas/gschemas.compiled”: open() failed: No such file or directory
# If any extension produces this error, it can be added in if statement below to solve the problem
# Fly-Pie or PaperWM
if [[ "${EXT_UUID}" == "flypie@schneegans.github.com" || "${EXT_UUID}" == "paperwm@paperwm.github.com" ]]; then
install -d -m 0755 "/usr/share/gnome-shell/extensions/${EXT_UUID}/schemas/"
install -D -p -m 0644 "${TMP_DIR}/schemas/"*.gschema.xml "/usr/share/gnome-shell/extensions/${EXT_UUID}/schemas/"
glib-compile-schemas "/usr/share/gnome-shell/extensions/${EXT_UUID}/schemas/" &>/dev/null
else
# Regular schema installation
install -d -m 0755 "/usr/share/glib-2.0/schemas/"
install -D -p -m 0644 "${TMP_DIR}/schemas/"*.gschema.xml "/usr/share/glib-2.0/schemas/"
fi
fi
# Install languages
# Locale is not crucial for extensions to work, as they will fallback to gschema.xml
# Some of them might not have any locale at the moment
# So that's why I made a check for directory
if [[ -d "${TMP_DIR}/locale" ]]; then
echo "Installing language extension files"
install -d -m 0755 "/usr/share/locale/"
cp -r "${TMP_DIR}/locale"/* "/usr/share/locale/"
fi
# Delete the temporary directory
echo "Cleaning up the temporary directory"
rm -r "${TMP_DIR}"
echo "Extension '${EXT_NAME}' is successfully installed"
echo "----------------------------------INSTALLATION DONE----------------------------------"
done
fi
if [[ ${#UNINSTALL[@]} -gt 0 ]]; then
for UNINSTALL_EXT in "${UNINSTALL[@]}"; do
if [[ ! "${UNINSTALL_EXT}" =~ ^[0-9]+$ ]]; then
# Literal-name extension config
# Replaces whitespaces with %20 for install entries which contain extension name, since URLs can't contain whitespace
# Getting json query from the website is useful to intuitively uninstall the extension without need to manually input UUID
WHITESPACE_HTML="${UNINSTALL_EXT// /%20}"
URL_QUERY=$(curl -sf "https://extensions.gnome.org/extension-query/?search=${WHITESPACE_HTML}")
QUERIED_EXT=$(echo "${URL_QUERY}" | jq ".extensions[] | select(.name == \"${UNINSTALL_EXT}\")")
if [[ -z "${QUERIED_EXT}" ]] || [[ "${QUERIED_EXT}" == "null" ]]; then
echo "ERROR: Extension '${UNINSTALL_EXT}' does not exist in https://extensions.gnome.org/ website"
echo " Extension name is case-sensitive, so be sure that you typed it correctly,"
echo " including the correct uppercase & lowercase characters"
exit 1
fi
EXT_UUID=$(echo "${QUERIED_EXT}" | jq -r '.["uuid"]')
EXT_NAME=$(echo "${QUERIED_EXT}" | jq -r '.["name"]')
else
# PK ID extension config fallback if specified
URL_QUERY=$(curl -sf "https://extensions.gnome.org/extension-info/?pk=${UNINSTALL_EXT}")
PK_EXT=$(echo "${URL_QUERY}" | jq -r '.["pk"]' 2>/dev/null)
if [[ -z "${PK_EXT}" ]] || [[ "${PK_EXT}" == "null" ]]; then
echo "ERROR: Extension with PK ID '${UNINSTALL_EXT}' does not exist in https://extensions.gnome.org/ website"
echo " Please assure that you typed the PK ID correctly,"
echo " and that it exists in Gnome extensions website"
exit 1
fi
EXT_UUID=$(echo "${URL_QUERY}" | jq -r '.["uuid"]')
EXT_NAME=$(echo "${URL_QUERY}" | jq -r '.["name"]')
fi
# This is where uninstall step goes, above step is reused from install part
EXT_FILES="/usr/share/gnome-shell/extensions/${EXT_UUID}"
UNINSTALL_METADATA="${EXT_FILES}/metadata.json"
GETTEXT_DOMAIN=$(jq -r '.["gettext-domain"]' < "${UNINSTALL_METADATA}")
SETTINGS_SCHEMA=$(jq -r '.["settings-schema"]' < "${UNINSTALL_METADATA}")
LANGUAGE_LOCATION="/usr/share/locale"
# If settings-schema YAML key exists, than use that, if it doesn't
# Than substract the schema ID before @ symbol
if [[ ! "${SETTINGS_SCHEMA}" == "null" ]]; then
SCHEMA_LOCATION="/usr/share/glib-2.0/schemas/${SETTINGS_SCHEMA}.gschema.xml"
else
SUBSTRACTED_UUID=$(echo "${EXT_UUID}" | cut -d'@' -f1)
SCHEMA_LOCATION="/usr/share/glib-2.0/schemas/org.gnome.shell.extensions.${SUBSTRACTED_UUID}.gschema.xml"
fi
# Remove languages
if [[ ! "${GETTEXT_DOMAIN}" == "null" ]]; then
find "${LANGUAGE_LOCATION}" -type f -name "${GETTEXT_DOMAIN}.mo" -exec rm {} \;
else
echo "There are no extension languages to remove, since extension doesn't contain them"
fi
# Remove gschema xml
if [[ ! "${SETTINGS_SCHEMA}" == "null" ]] && [[ -f "${SCHEMA_LOCATION}" ]]; then
rm "${SCHEMA_LOCATION}"
else
echo "There is no gschema xml to remove, since extension doesn't have any settings"
fi
# Removing main extension files
if [[ -d "${EXT_FILES}" ]]; then
echo "Removing main extension files"
rm -r "${EXT_FILES}"
else
echo "ERROR: There are no main extension files to remove from the base image"
echo " It is possible that the extension that you inputted is not actually installed"
exit 1
fi
echo "----------------------------------UNINSTALLATION DONE----------------------------------"
done
fi
# Compile gschema to include schemas from extensions & to refresh schema state after uninstall is done
echo "Compiling gschema to include extension schemas & to refresh the schema state"
glib-compile-schemas "/usr/share/glib-2.0/schemas/" &>/dev/null