e0fad0e82f
Rediseño de apply_chromium_extension_policy y nueva función de purga in-place, tras resolver por qué las extensiones bloqueadas reaparecían en Chromium 148. - apply_chromium_extension_policy: añade --block (ExtensionInstallBlocklist). Reemplaza el modo ExtensionSettings "*": blocked (que rompía las extensiones unpacked vía --load-extension, p.ej. la de captura de web_proxy con el error 'Loading of unpacked extensions is disabled by the administrator') por una blocklist específica. FIX RAÍZ: los backups se guardan fuera de policies/managed/ (en policy-backups/), porque Chromium lee TODOS los archivos del directorio managed/ sin filtrar extensión de nombre — un extensions.json.bak ahí se mergea con la policy y reinyecta las extensiones del backup (location=7). - clean_chrome_profile_extensions (nueva): purga in-place de un perfil existente (borra carpetas de Extensions/ + refs en Preferences/Secure Preferences) dejando solo la whitelist. Complementa la policy: la policy evita reinstalación, esta desinstala lo ya presente. Requiere chromium cerrado. Ambas: dominio browser, grupo navegator, guard de auto-ejecución, dry-run.
246 lines
10 KiB
Bash
246 lines
10 KiB
Bash
#!/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 <dir>] [--profile-directory <name>]
|
|
[--keep <ext_id>]... [--dry-run]
|
|
|
|
--user-data-dir Raíz del perfil. Default: ~/.config/chromium
|
|
--profile-directory Subperfil. Default: Default
|
|
--keep <ext_id> 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.<id>
|
|
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.<id> (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 <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 <profile_path> <kept_nameref> <removed_nameref>
|
|
_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
|