Files
gnome-user-switcher/global-extension-installer/scripts/apply-extension-defaults.sh
2026-03-01 11:44:02 +01:00

462 lines
11 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
DEFAULT_USERS=(kari owen romy)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SYSTEM_EXT_DIR="/usr/share/gnome-shell/extensions"
DEFAULT_EXTENSIONS_FILE="${SCRIPT_DIR}/extensions.txt"
DCONF_PROFILE_FILE="/etc/dconf/profile/user"
DCONF_DEFAULTS_FILE="/etc/dconf/db/local.d/00-gnome-extensions"
DCONF_LOCKS_FILE="/etc/dconf/db/local.d/locks/gnome-extensions"
DRY_RUN=0
LOCK_DEFAULT=0
REFRESH_EXISTING=0
USERS=("${DEFAULT_USERS[@]}")
EXTENSIONS_FILE="${DEFAULT_EXTENSIONS_FILE}"
EXTENSION_UUIDS=()
EXTENSION_SOURCES=()
EXTENSIONS_BASE_DIR=""
print_usage() {
cat <<USAGE
Usage: sudo bash scripts/apply-extension-defaults.sh [options]
Options:
--extensions-file PATH Extensions list file (default: scripts/extensions.txt)
--users user1,user2 Override target users (default: kari,owen,romy)
--lock-default Lock dconf defaults
--refresh-existing Reinstall even if extension dir already exists
--dry-run Print actions without writing changes
-h, --help Show help
extensions.txt format:
<uuid> <source>
Supported source values:
- local zip path (relative or absolute)
- zip URL (https://...zip)
- git URL (https://...git)
- git URL with subdir (https://...git#path/to/extension)
USAGE
}
run_cmd() {
if [[ $DRY_RUN -eq 1 ]]; then
printf '[dry-run] '
printf '%q ' "$@"
echo
return 0
fi
"$@"
}
run_cmd_may_fail() {
if [[ $DRY_RUN -eq 1 ]]; then
printf '[dry-run] '
printf '%q ' "$@"
echo
return 0
fi
"$@"
}
write_file() {
local path="$1"
local content="$2"
if [[ $DRY_RUN -eq 1 ]]; then
echo "[dry-run] write ${path}"
echo "$content"
return 0
fi
mkdir -p "$(dirname "$path")"
printf '%s\n' "$content" > "$path"
}
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--extensions-file)
EXTENSIONS_FILE="$2"
shift 2
;;
--users)
IFS=',' read -r -a USERS <<< "$2"
shift 2
;;
--lock-default)
LOCK_DEFAULT=1
shift
;;
--refresh-existing)
REFRESH_EXISTING=1
shift
;;
--dry-run)
DRY_RUN=1
shift
;;
-h|--help)
print_usage
exit 0
;;
*)
echo "Unknown argument: $1" >&2
print_usage
exit 1
;;
esac
done
}
ensure_root() {
if [[ $DRY_RUN -eq 1 ]]; then
return 0
fi
if [[ ${EUID:-$(id -u)} -ne 0 ]]; then
echo "Run as root (sudo) unless using --dry-run." >&2
exit 1
fi
}
extract_uuid() {
local metadata_file="$1"
sed -n 's/.*"uuid"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$metadata_file" | head -n1
}
resolve_source_path() {
local source="$1"
if [[ "$source" = /* ]]; then
printf '%s' "$source"
else
printf '%s/%s' "$EXTENSIONS_BASE_DIR" "$source"
fi
}
is_zip_source() {
local source="$1"
[[ "$source" == *.zip ]] || [[ "$source" == *.zip\?* ]]
}
is_url_source() {
local source="$1"
[[ "$source" =~ ^https?:// ]]
}
normalize_extension_permissions() {
local target_dir="$1"
run_cmd chown -R root:root "$target_dir"
run_cmd find "$target_dir" -type d -exec chmod 755 {} +
run_cmd find "$target_dir" -type f -exec chmod 644 {} +
}
copy_dir_into_system_extensions() {
local source_dir="$1"
local uuid="$2"
local target_dir="${SYSTEM_EXT_DIR}/${uuid}"
if [[ ! -f "$source_dir/metadata.json" ]]; then
echo "Invalid extension source for $uuid: metadata.json missing in $source_dir" >&2
return 1
fi
if [[ "$(extract_uuid "$source_dir/metadata.json")" != "$uuid" ]]; then
echo "UUID mismatch for $uuid in $source_dir/metadata.json" >&2
return 1
fi
run_cmd mkdir -p "$target_dir"
run_cmd cp -a "$source_dir/." "$target_dir/"
normalize_extension_permissions "$target_dir"
echo "System-wide extension ensured: $uuid"
}
resolve_extension_dir_from_tree() {
local tree_root="$1"
local uuid="$2"
local subdir="$3"
local candidate
local metadata
if [[ -n "$subdir" ]]; then
candidate="$tree_root/$subdir"
if [[ -f "$candidate/metadata.json" ]] && [[ "$(extract_uuid "$candidate/metadata.json")" == "$uuid" ]]; then
printf '%s' "$candidate"
return 0
fi
echo "Subdir '$subdir' does not contain extension $uuid" >&2
return 1
fi
if [[ -f "$tree_root/metadata.json" ]] && [[ "$(extract_uuid "$tree_root/metadata.json")" == "$uuid" ]]; then
printf '%s' "$tree_root"
return 0
fi
while IFS= read -r metadata; do
if [[ "$(extract_uuid "$metadata")" == "$uuid" ]]; then
printf '%s' "$(dirname "$metadata")"
return 0
fi
done < <(find "$tree_root" -maxdepth 6 -type f -name metadata.json)
echo "Could not locate extension $uuid in source tree" >&2
return 1
}
install_from_zip_source() {
local uuid="$1"
local source="$2"
local work_dir
local zip_path
local extract_dir
local ext_dir
if [[ $DRY_RUN -eq 1 ]]; then
if is_url_source "$source"; then
run_cmd curl -fsSL "$source" -o /tmp/extension.zip
run_cmd unzip -q /tmp/extension.zip -d /tmp/extension-unzip
else
zip_path="$(resolve_source_path "$source")"
run_cmd unzip -q "$zip_path" -d /tmp/extension-unzip
fi
echo "[dry-run] resolve extension directory for $uuid from zip"
run_cmd mkdir -p "${SYSTEM_EXT_DIR}/${uuid}"
run_cmd cp -a /tmp/extension-unzip/. "${SYSTEM_EXT_DIR}/${uuid}/"
normalize_extension_permissions "${SYSTEM_EXT_DIR}/${uuid}"
echo "System-wide extension ensured: $uuid"
return 0
fi
work_dir="$(mktemp -d)"
extract_dir="$work_dir/unzip"
mkdir -p "$extract_dir"
if is_url_source "$source"; then
zip_path="$work_dir/source.zip"
curl -fsSL "$source" -o "$zip_path"
else
zip_path="$(resolve_source_path "$source")"
if [[ ! -f "$zip_path" ]]; then
echo "Missing zip source for $uuid: $zip_path" >&2
rm -rf "$work_dir"
return 1
fi
fi
unzip -q "$zip_path" -d "$extract_dir"
ext_dir="$(resolve_extension_dir_from_tree "$extract_dir" "$uuid" "")"
copy_dir_into_system_extensions "$ext_dir" "$uuid"
rm -rf "$work_dir"
}
install_from_git_source() {
local uuid="$1"
local source="$2"
local repo_url
local subdir=""
local work_dir
local repo_dir
local ext_dir
repo_url="${source%%#*}"
if [[ "$source" == *"#"* ]]; then
subdir="${source#*#}"
fi
if [[ $DRY_RUN -eq 1 ]]; then
run_cmd git clone --depth 1 "$repo_url" /tmp/extension-repo
echo "[dry-run] resolve extension directory for $uuid (subdir: ${subdir:-auto})"
run_cmd mkdir -p "${SYSTEM_EXT_DIR}/${uuid}"
run_cmd cp -a /tmp/extension-repo/. "${SYSTEM_EXT_DIR}/${uuid}/"
normalize_extension_permissions "${SYSTEM_EXT_DIR}/${uuid}"
echo "System-wide extension ensured: $uuid"
return 0
fi
work_dir="$(mktemp -d)"
repo_dir="$work_dir/repo"
git clone --depth 1 "$repo_url" "$repo_dir" >/dev/null 2>&1
ext_dir="$(resolve_extension_dir_from_tree "$repo_dir" "$uuid" "$subdir")"
copy_dir_into_system_extensions "$ext_dir" "$uuid"
rm -rf "$work_dir"
}
install_extension() {
local uuid="$1"
local source="$2"
local target_dir="${SYSTEM_EXT_DIR}/${uuid}"
if [[ -f "$target_dir/metadata.json" && $REFRESH_EXISTING -eq 0 ]]; then
normalize_extension_permissions "$target_dir"
echo "System-wide extension already present: $uuid"
return 0
fi
if is_zip_source "$source"; then
install_from_zip_source "$uuid" "$source"
return
fi
if is_url_source "$source"; then
install_from_git_source "$uuid" "$source"
return
fi
echo "Unsupported source for $uuid: $source" >&2
return 1
}
load_extensions_file() {
local file="$1"
local line
local lineno=0
local uuid
local source
if [[ ! -f "$file" ]]; then
echo "Missing extensions file: $file" >&2
exit 1
fi
EXTENSIONS_BASE_DIR="$(cd "$(dirname "$file")" && pwd)"
while IFS= read -r line || [[ -n "$line" ]]; do
lineno=$((lineno + 1))
line="${line%%$'\r'}"
[[ -z "$line" ]] && continue
[[ "${line:0:1}" == "#" ]] && continue
uuid="${line%%[[:space:]]*}"
source="${line#"$uuid"}"
source="$(echo "$source" | sed 's/^[[:space:]]*//')"
if [[ -z "$uuid" || -z "$source" || "$uuid" == "$line" ]]; then
echo "Invalid line ${lineno} in ${file}: expected '<uuid> <source>'" >&2
exit 1
fi
EXTENSION_UUIDS+=("$uuid")
EXTENSION_SOURCES+=("$source")
done < "$file"
if [[ ${#EXTENSION_UUIDS[@]} -eq 0 ]]; then
echo "No extensions loaded from ${file}" >&2
exit 1
fi
echo "Loaded ${#EXTENSION_UUIDS[@]} extension entries from ${file}"
}
install_system_wide_extensions() {
local i
local failures=0
for i in "${!EXTENSION_UUIDS[@]}"; do
if ! install_extension "${EXTENSION_UUIDS[$i]}" "${EXTENSION_SOURCES[$i]}"; then
echo "Failed to install ${EXTENSION_UUIDS[$i]} from ${EXTENSION_SOURCES[$i]}" >&2
failures=$((failures + 1))
fi
done
if [[ $failures -gt 0 ]]; then
echo "System-wide install failed for ${failures} extension(s)." >&2
return 1
fi
}
json_array_from_uuids() {
local out="["
local first=1
local uuid
for uuid in "${EXTENSION_UUIDS[@]}"; do
if [[ $first -eq 0 ]]; then
out+=", "
fi
out+="'${uuid}'"
first=0
done
out+="]"
printf '%s' "$out"
}
apply_dconf_defaults() {
local extensions_variant
extensions_variant="$(json_array_from_uuids)"
if [[ -f "$DCONF_PROFILE_FILE" ]]; then
if [[ $DRY_RUN -eq 1 ]]; then
echo "[dry-run] ensure ${DCONF_PROFILE_FILE} contains system-db:local"
elif ! rg -q '^system-db:local$' "$DCONF_PROFILE_FILE"; then
printf '%s\n' 'system-db:local' >> "$DCONF_PROFILE_FILE"
fi
else
write_file "$DCONF_PROFILE_FILE" $'user-db:user\nsystem-db:local'
fi
write_file "$DCONF_DEFAULTS_FILE" "[org/gnome/shell]
enabled-extensions=${extensions_variant}
disable-user-extensions=false"
if [[ $LOCK_DEFAULT -eq 1 ]]; then
write_file "$DCONF_LOCKS_FILE" $'/org/gnome/shell/enabled-extensions\n/org/gnome/shell/disable-user-extensions'
fi
run_cmd dconf update
}
apply_user_settings() {
local extensions_variant
extensions_variant="$(json_array_from_uuids)"
local failures=0
local user
local uuid
for user in "${USERS[@]}"; do
if ! id "$user" >/dev/null 2>&1; then
echo "Skipping unknown user: $user"
continue
fi
if run_cmd_may_fail runuser -u "$user" -- dbus-run-session -- gsettings set org.gnome.shell enabled-extensions "$extensions_variant" \
&& run_cmd_may_fail runuser -u "$user" -- dbus-run-session -- gsettings set org.gnome.shell disable-user-extensions false; then
echo "Applied extension list for user: $user"
else
echo "Primary gsettings path failed for user: $user, trying dconf fallback"
if run_cmd_may_fail runuser -u "$user" -- dbus-run-session -- dconf write /org/gnome/shell/enabled-extensions "$extensions_variant" \
&& run_cmd_may_fail runuser -u "$user" -- dbus-run-session -- dconf write /org/gnome/shell/disable-user-extensions false; then
echo "Applied extension list for user via dconf fallback: $user"
else
echo "Failed to apply extension list for user: $user" >&2
failures=$((failures + 1))
fi
fi
for uuid in "${EXTENSION_UUIDS[@]}"; do
run_cmd_may_fail runuser -u "$user" -- dbus-run-session -- gnome-extensions enable "$uuid" >/dev/null 2>&1 || true
done
done
if [[ $failures -gt 0 ]]; then
echo "One or more user updates failed (${failures})." >&2
return 1
fi
}
parse_args "$@"
ensure_root
load_extensions_file "$EXTENSIONS_FILE"
install_system_wide_extensions
apply_dconf_defaults
apply_user_settings
echo "System-wide extension install complete, defaults configured for new users, settings applied to selected existing users."
echo "Users should log out and back in once to ensure GNOME Shell loads the new defaults cleanly."