#!/usr/bin/env bash # clean_chrome_profile_extensions — purga in-place extensiones fuera de la whitelist # de un perfil Chrome/Chromium existente. Borra las carpetas de disco y limpia las # referencias en Preferences y Secure Preferences para que Chromium no las reinstale. set -euo pipefail clean_chrome_profile_extensions() { # ── defaults ────────────────────────────────────────────────────────────── local _user_data_dir="${HOME}/.config/chromium" local _profile_dir="Default" local _keep=() local _default_ext="ddkjiahejlhfcafbddmgiahcphecmpfh" local _dry_run=0 # ── parse args ───────────────────────────────────────────────────────────── _usage() { cat >&2 <<'EOF' Usage: clean_chrome_profile_extensions [--user-data-dir ] [--profile-directory ] [--keep ]... [--dry-run] --user-data-dir Raíz del perfil. Default: ~/.config/chromium --profile-directory Subperfil. Default: Default --keep ID de extensión a conservar (repetible). Default si no se pasa ninguno: ddkjiahejlhfcafbddmgiahcphecmpfh (uBlock Origin Lite) --dry-run Lista qué se borraría sin tocar nada. Exit codes: 0 éxito (o dry-run completado) 1 error de argumento o validación 2 chromium está corriendo (solo en modo real) 3 directorio de extensiones no encontrado EOF return 1 } while [[ $# -gt 0 ]]; do case "$1" in --user-data-dir) _user_data_dir="$2"; shift 2 ;; --profile-directory) _profile_dir="$2"; shift 2 ;; --keep) _keep+=("$2"); shift 2 ;; --dry-run) _dry_run=1; shift ;; -h|--help) _usage; return 0 ;; *) echo "clean_chrome_profile_extensions: argumento desconocido: $1" >&2; return 1 ;; esac done # ── whitelist por defecto ────────────────────────────────────────────────── if [[ ${#_keep[@]} -eq 0 ]]; then _keep=("$_default_ext") fi # ── construir paths base ─────────────────────────────────────────────────── local _profile_path _profile_path="${_user_data_dir}/${_profile_dir}" local _ext_dir="${_profile_path}/Extensions" # ── validaciones ────────────────────────────────────────────────────────── if [[ ! -d "$_profile_path" ]]; then echo "clean_chrome_profile_extensions: perfil no encontrado: ${_profile_path}" >&2 return 1 fi if [[ ! -d "$_ext_dir" ]]; then echo "clean_chrome_profile_extensions: directorio de extensiones no encontrado: ${_ext_dir}" >&2 return 3 fi # ── guard: chromium NO debe estar corriendo (excepto en dry-run) ────────── if [[ $_dry_run -eq 0 ]]; then if pgrep -x chromium >/dev/null 2>&1; then echo "clean_chrome_profile_extensions: chromium está corriendo — ciérralo antes de limpiar:" >&2 echo " pkill -TERM chromium" >&2 echo "(Chromium reescribe Preferences desde memoria al cerrar y desharía la purga)" >&2 return 2 fi fi # ── enumerar extensiones instaladas ─────────────────────────────────────── local _to_remove=() local _to_keep=() while IFS= read -r -d '' _ext_path; do local _ext_id _ext_id="$(basename "$_ext_path")" # Siempre ignorar la carpeta Temp (usada durante installs en curso) if [[ "$_ext_id" == "Temp" ]]; then continue fi # Comprobar si está en la whitelist local _in_keep=0 local _k for _k in "${_keep[@]}"; do if [[ "$_ext_id" == "$_k" ]]; then _in_keep=1 break fi done if [[ $_in_keep -eq 1 ]]; then _to_keep+=("$_ext_id") else _to_remove+=("$_ext_id") fi done < <(find "$_ext_dir" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z) # ── modo dry-run: solo informar ──────────────────────────────────────────── if [[ $_dry_run -eq 1 ]]; then echo "=== clean_chrome_profile_extensions DRY-RUN ===" >&2 echo " Perfil : ${_profile_path}" >&2 echo " Conservar (${#_to_keep[@]}): ${_to_keep[*]+"${_to_keep[*]}"}" >&2 echo " Borrar (${#_to_remove[@]}): ${_to_remove[*]+"${_to_remove[*]}"}" >&2 _emit_json "$_profile_path" _to_keep _to_remove return 0 fi # ── borrar extensiones fuera de la whitelist ─────────────────────────────── if [[ ${#_to_remove[@]} -gt 0 ]]; then local _id for _id in "${_to_remove[@]}"; do rm -rf "${_ext_dir}/${_id}" done # ── purgar referencias en Preferences y Secure Preferences ──────────── # Construir lista Python de IDs eliminados local _py_ids_list="" for _id in "${_to_remove[@]}"; do _py_ids_list+="\"${_id}\"," done _py_ids_list="[${_py_ids_list%,}]" local _today _today="$(date +%Y%m%d)" local _prefs_file for _prefs_file in "${_profile_path}/Preferences" "${_profile_path}/Secure Preferences"; do if [[ ! -f "$_prefs_file" ]]; then continue fi # Backup (no sobreescribir backup del mismo día) local _backup="${_prefs_file}.bak.${_today}" if [[ ! -f "$_backup" ]]; then cp "$_prefs_file" "$_backup" fi # Editar con python3 si está disponible if command -v python3 >/dev/null 2>&1; then python3 - "$_prefs_file" "$_py_ids_list" <<'PY' || \ echo "clean_chrome_profile_extensions: advertencia — no se pudo purgar ${_prefs_file} con python3" >&2 import sys, json prefs_path = sys.argv[1] removed_ids = json.loads(sys.argv[2]) with open(prefs_path, "r", encoding="utf-8") as f: data = json.load(f) # 1. extensions.settings. ext_settings = data.get("extensions", {}).get("settings", {}) for ext_id in removed_ids: ext_settings.pop(ext_id, None) # 2. extensions.pinned_extensions (lista de IDs) pinned = data.get("extensions", {}).get("pinned_extensions", None) if isinstance(pinned, list): data["extensions"]["pinned_extensions"] = [ pid for pid in pinned if pid not in removed_ids ] # 3. protection.macs.extensions.settings. (Secure Preferences HMAC table) try: mac_ext = data["protection"]["macs"]["extensions"]["settings"] for ext_id in removed_ids: mac_ext.pop(ext_id, None) except (KeyError, TypeError): pass with open(prefs_path, "w", encoding="utf-8") as f: json.dump(data, f, separators=(",", ":")) PY # Fallback con jq si python3 no está disponible elif command -v jq >/dev/null 2>&1; then local _tmp_prefs _tmp_prefs="$(mktemp)" local _jq_del="" for _id in "${_to_remove[@]}"; do _jq_del+=" | del(.extensions.settings[\"${_id}\"])" _jq_del+=" | del(.protection.macs.extensions.settings[\"${_id}\"])" done # pinned_extensions como lista _jq_del+=" | if .extensions.pinned_extensions then .extensions.pinned_extensions -= [$(printf '"%s",' "${_to_remove[@]}" | sed 's/,$//')] else . end" jq "${_jq_del:1}" "$_prefs_file" > "$_tmp_prefs" && mv "$_tmp_prefs" "$_prefs_file" || { echo "clean_chrome_profile_extensions: advertencia — jq falló procesando ${_prefs_file}" >&2 rm -f "$_tmp_prefs" } else echo "clean_chrome_profile_extensions: advertencia — ni python3 ni jq disponibles; se borraron las carpetas pero no las referencias en $(basename "$_prefs_file")" >&2 fi done fi # ── emitir resultado JSON ────────────────────────────────────────────────── _emit_json "$_profile_path" _to_keep _to_remove } # ── helpers ──────────────────────────────────────────────────────────────────── # _json_array_from_nameref # Convierte un array bash (pasado por nombre de variable) en JSON array de strings. _json_array_from_nameref() { local -n _arr_ref="$1" local _out="[" local _first=1 local _item for _item in "${_arr_ref[@]+"${_arr_ref[@]}"}"; do if [[ $_first -eq 1 ]]; then _out+="\"${_item}\"" _first=0 else _out+=",\"${_item}\"" fi done _out+="]" echo "$_out" } # _emit_json _emit_json() { local _p="$1" local _kept_json _kept_json="$(_json_array_from_nameref "$2")" local _removed_json _removed_json="$(_json_array_from_nameref "$3")" printf '{"profile":"%s","kept":%s,"removed":%s}\n' \ "$_p" "$_kept_json" "$_removed_json" } # ── auto-ejecución ──────────────────────────────────────────────────────────── if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then clean_chrome_profile_extensions "$@" fi