685224ccb2
Bug descubierto al ejecutar el reset real: los guards y los kills usaban 'pgrep -af [c]hromium | grep -F <udd>'. Como la ruta del user-data-dir contiene la cadena 'chromium' (~/.config/chromium-cdp), el propio proceso grep/ugrep —cuyo cmdline incluye <udd>— era capturado por pgrep, dando un falso positivo perpetuo: el guard creía siempre que había un chromium abierto y delete/restore abortaban con exit 2, y el lazo de cierre nunca convergía. Fix en delete_chrome_profile, restore_chrome_bookmarks, create_chrome_profile y el pipeline reset_chrome_profiles: enumerar por PID con 'pgrep -x chromium' (comm exactamente 'chromium', nunca grep/pgrep/bash) y leer /proc/PID/cmdline para comprobar el udd. Validado: reset destructivo real de los 4 perfiles completó OK, cada perfil quedó con solo uBlock + web_proxy y los bookmarks restaurados.
265 lines
12 KiB
Bash
265 lines
12 KiB
Bash
#!/usr/bin/env bash
|
|
# delete_chrome_profile — borra por completo uno o varios perfiles Chrome/Chromium:
|
|
# elimina la carpeta del perfil y limpia todas las referencias en Local State
|
|
# (info_cache, profiles_order, last_active_profiles, last_used, variations_google_groups).
|
|
|
|
set -euo pipefail
|
|
|
|
delete_chrome_profile() {
|
|
# ── defaults ──────────────────────────────────────────────────────────────
|
|
local _user_data_dir=""
|
|
local _profiles=()
|
|
local _dry_run=0
|
|
|
|
# ── parse args ─────────────────────────────────────────────────────────────
|
|
_usage() {
|
|
cat >&2 <<'EOF'
|
|
Usage: delete_chrome_profile --user-data-dir <dir> --profile <name> [--profile <name>]... [--dry-run]
|
|
|
|
--user-data-dir <dir> Ruta raíz del user-data-dir de Chrome/Chromium (obligatorio).
|
|
--profile <name> Nombre de la carpeta del perfil, ej. "Default" o "Profile 1"
|
|
(repetible, al menos uno obligatorio).
|
|
--dry-run Muestra qué borraría y qué claves de Local State quitarí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)
|
|
EOF
|
|
return 1
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--user-data-dir) _user_data_dir="$2"; shift 2 ;;
|
|
--profile) _profiles+=("$2"); shift 2 ;;
|
|
--dry-run) _dry_run=1; shift ;;
|
|
-h|--help) _usage; return 0 ;;
|
|
*) echo "delete_chrome_profile: argumento desconocido: $1" >&2; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
# ── validaciones de argumentos ────────────────────────────────────────────
|
|
if [[ -z "$_user_data_dir" ]]; then
|
|
echo "delete_chrome_profile: --user-data-dir es obligatorio" >&2
|
|
return 1
|
|
fi
|
|
|
|
if [[ ${#_profiles[@]} -eq 0 ]]; then
|
|
echo "delete_chrome_profile: se requiere al menos un --profile" >&2
|
|
return 1
|
|
fi
|
|
|
|
if [[ ! -d "$_user_data_dir" ]]; then
|
|
echo "delete_chrome_profile: user-data-dir no encontrado: ${_user_data_dir}" >&2
|
|
return 1
|
|
fi
|
|
|
|
local _local_state="${_user_data_dir}/Local State"
|
|
if [[ ! -f "$_local_state" ]]; then
|
|
echo "delete_chrome_profile: Local State no encontrado: ${_local_state}" >&2
|
|
return 1
|
|
fi
|
|
|
|
# ── guard: ningún chromium debe tener ESTE user-data-dir abierto (excepto en dry-run) ──
|
|
# Por-udd, no global. Comprobamos por PID con comm=chromium (pgrep -x) y leemos su cmdline,
|
|
# para NO auto-matchear el propio `grep`/`pgrep` del pipe: como el path del udd contiene la
|
|
# cadena "chromium" (p.ej. ~/.config/chromium-cdp), un `pgrep -af '[c]hromium' | grep <udd>`
|
|
# se detecta a sí mismo. pgrep -x chromium solo lista procesos cuyo nombre es exactamente
|
|
# "chromium" (el navegador), nunca grep/pgrep/bash.
|
|
if [[ $_dry_run -eq 0 ]]; then
|
|
local _p _busy=0
|
|
for _p in $(pgrep -x chromium 2>/dev/null); do
|
|
if tr '\0' ' ' < "/proc/$_p/cmdline" 2>/dev/null | grep -qF -- "$_user_data_dir"; then
|
|
_busy=1; break
|
|
fi
|
|
done
|
|
if [[ $_busy -eq 1 ]]; then
|
|
echo "delete_chrome_profile: hay un chromium con este user-data-dir abierto — ciérralo antes de borrar perfiles:" >&2
|
|
echo " pkill -TERM chromium" >&2
|
|
echo "(Chromium reescribe Local State desde memoria al cerrar y desharía el borrado)" >&2
|
|
return 2
|
|
fi
|
|
fi
|
|
|
|
local _today
|
|
_today="$(date +%Y%m%d)"
|
|
|
|
# ── modo dry-run ──────────────────────────────────────────────────────────
|
|
if [[ $_dry_run -eq 1 ]]; then
|
|
echo "=== delete_chrome_profile DRY-RUN ===" >&2
|
|
local _p
|
|
for _p in "${_profiles[@]}"; do
|
|
local _pdir="${_user_data_dir}/${_p}"
|
|
if [[ -d "$_pdir" ]]; then
|
|
echo " [borraría] rm -rf ${_pdir}" >&2
|
|
else
|
|
echo " [no existe] ${_pdir}" >&2
|
|
fi
|
|
echo " [Local State] quitaría claves para perfil: '${_p}'" >&2
|
|
echo " profile.info_cache.${_p}" >&2
|
|
echo " profile.profiles_order (entrada '${_p}')" >&2
|
|
echo " profile.last_active_profiles (entrada '${_p}')" >&2
|
|
echo " profile.last_used (si == '${_p}', reasignar)" >&2
|
|
echo " variations_google_groups.${_p} (si existe)" >&2
|
|
done
|
|
|
|
# Construir JSON de dry-run inline
|
|
local _dry_items="" _dry_first=1
|
|
for _p in "${_profiles[@]}"; do
|
|
local _pdir="${_user_data_dir}/${_p}"
|
|
local _sep="" _exists="false"
|
|
[[ $_dry_first -eq 0 ]] && _sep=","
|
|
_dry_first=0
|
|
[[ -d "$_pdir" ]] && _exists="true"
|
|
_dry_items+="${_sep}{\"profile\":\"${_p}\",\"dir_exists\":${_exists},\"would_remove\":${_exists},\"local_state_would_clean\":true}"
|
|
done
|
|
printf '{"dry_run":true,"would_delete":[%s]}\n' "$_dry_items"
|
|
return 0
|
|
fi
|
|
|
|
# ── backup de Local State (no sobreescribir el del día) ───────────────────
|
|
local _backup="${_local_state}.bak.${_today}"
|
|
if [[ ! -f "$_backup" ]]; then
|
|
cp "$_local_state" "$_backup"
|
|
fi
|
|
|
|
# ── borrar carpetas de perfil ──────────────────────────────────────────────
|
|
local _deleted_results=() # "profile|dir_removed|ls_cleaned"
|
|
local _p
|
|
for _p in "${_profiles[@]}"; do
|
|
local _pdir="${_user_data_dir}/${_p}"
|
|
local _dir_removed=false
|
|
if [[ -d "$_pdir" ]]; then
|
|
rm -rf "$_pdir"
|
|
_dir_removed=true
|
|
fi
|
|
_deleted_results+=("${_p}|${_dir_removed}|false")
|
|
done
|
|
|
|
# ── construir lista Python de perfiles a eliminar ─────────────────────────
|
|
local _py_profiles_list=""
|
|
for _p in "${_profiles[@]}"; do
|
|
_py_profiles_list+="\"${_p}\","
|
|
done
|
|
_py_profiles_list="[${_py_profiles_list%,}]"
|
|
|
|
# ── editar Local State con python3 ────────────────────────────────────────
|
|
local _ls_cleaned=false
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
python3 - "$_local_state" "$_py_profiles_list" <<'PY'
|
|
import sys, json
|
|
|
|
ls_path = sys.argv[1]
|
|
profiles_to_delete = json.loads(sys.argv[2])
|
|
|
|
with open(ls_path, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
|
|
profile_section = data.get("profile", {})
|
|
|
|
# 1. profile.info_cache — eliminar cada perfil
|
|
info_cache = profile_section.get("info_cache", {})
|
|
for p in profiles_to_delete:
|
|
info_cache.pop(p, None)
|
|
|
|
# 2. profile.profiles_order — quitar entradas del perfil
|
|
if "profiles_order" in profile_section and isinstance(profile_section["profiles_order"], list):
|
|
profile_section["profiles_order"] = [
|
|
x for x in profile_section["profiles_order"] if x not in profiles_to_delete
|
|
]
|
|
|
|
# 3. profile.last_active_profiles — quitar entradas del perfil
|
|
if "last_active_profiles" in profile_section and isinstance(profile_section["last_active_profiles"], list):
|
|
profile_section["last_active_profiles"] = [
|
|
x for x in profile_section["last_active_profiles"] if x not in profiles_to_delete
|
|
]
|
|
|
|
# 4. profile.last_used — reasignar si apunta a un perfil borrado
|
|
last_used = profile_section.get("last_used", "")
|
|
if last_used in profiles_to_delete:
|
|
remaining = [k for k in info_cache.keys() if k not in profiles_to_delete]
|
|
profile_section["last_used"] = remaining[0] if remaining else ""
|
|
|
|
# 5. variations_google_groups — limpiar entradas del perfil (si existe)
|
|
vgg = data.get("variations_google_groups", {})
|
|
for p in profiles_to_delete:
|
|
vgg.pop(p, None)
|
|
|
|
with open(ls_path, "w", encoding="utf-8") as f:
|
|
json.dump(data, f, separators=(",", ":"))
|
|
PY
|
|
_ls_cleaned=true
|
|
|
|
# ── fallback con jq ───────────────────────────────────────────────────────
|
|
elif command -v jq >/dev/null 2>&1; then
|
|
local _tmp_ls
|
|
_tmp_ls="$(mktemp)"
|
|
local _jq_expr="."
|
|
for _p in "${_profiles[@]}"; do
|
|
_jq_expr+=" | del(.profile.info_cache[\"${_p}\"])"
|
|
_jq_expr+=" | del(.variations_google_groups[\"${_p}\"])"
|
|
_jq_expr+=" | if .profile.profiles_order then .profile.profiles_order -= [\"${_p}\"] else . end"
|
|
_jq_expr+=" | if .profile.last_active_profiles then .profile.last_active_profiles -= [\"${_p}\"] else . end"
|
|
done
|
|
if jq "${_jq_expr}" "$_local_state" > "$_tmp_ls" 2>/dev/null; then
|
|
mv "$_tmp_ls" "$_local_state"
|
|
_ls_cleaned=true
|
|
else
|
|
echo "delete_chrome_profile: advertencia — jq falló editando Local State" >&2
|
|
rm -f "$_tmp_ls"
|
|
fi
|
|
|
|
else
|
|
echo "delete_chrome_profile: advertencia — ni python3 ni jq disponibles; carpetas borradas pero Local State no modificado" >&2
|
|
fi
|
|
|
|
# ── validar que el JSON resultante sigue siendo parseable ─────────────────
|
|
if [[ "$_ls_cleaned" == "true" ]]; then
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
if ! python3 -c "import sys, json; json.load(open(sys.argv[1]))" "$_local_state" 2>/dev/null; then
|
|
echo "delete_chrome_profile: JSON de Local State inválido tras edición — restaurando backup" >&2
|
|
cp "$_backup" "$_local_state"
|
|
return 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# ── actualizar _deleted_results con ls_cleaned ────────────────────────────
|
|
local _updated_results=()
|
|
for _entry in "${_deleted_results[@]}"; do
|
|
local _ep _edr _els
|
|
IFS='|' read -r _ep _edr _els <<< "$_entry"
|
|
_updated_results+=("${_ep}|${_edr}|${_ls_cleaned}")
|
|
done
|
|
|
|
# ── leer last_used resultante ──────────────────────────────────────────────
|
|
local _new_last_used=""
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
_new_last_used="$(python3 -c "
|
|
import sys, json
|
|
data = json.load(open(sys.argv[1]))
|
|
print(data.get('profile', {}).get('last_used', ''))
|
|
" "$_local_state" 2>/dev/null || echo "")"
|
|
fi
|
|
|
|
# ── construir JSON de resultado inline ────────────────────────────────────
|
|
local _result_items="" _res_first=1
|
|
for _entry in "${_updated_results[@]+"${_updated_results[@]}"}"; do
|
|
local _pn _dr _lc
|
|
IFS='|' read -r _pn _dr _lc <<< "$_entry"
|
|
local _rsep=""
|
|
[[ $_res_first -eq 0 ]] && _rsep=","
|
|
_res_first=0
|
|
_result_items+="${_rsep}{\"profile\":\"${_pn}\",\"dir_removed\":${_dr},\"local_state_cleaned\":${_lc}}"
|
|
done
|
|
printf '{"deleted":[%s],"last_used":"%s","backup":"Local State.bak.%s"}\n' \
|
|
"$_result_items" "$_new_last_used" "$_today"
|
|
}
|
|
|
|
# ── auto-ejecución ────────────────────────────────────────────────────────────
|
|
if [[ "${BASH_SOURCE[0]:-}" == "${0}" ]]; then
|
|
delete_chrome_profile "$@"
|
|
fi
|