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.
310 lines
14 KiB
Bash
310 lines
14 KiB
Bash
#!/usr/bin/env bash
|
|
# create_chrome_profile — crea un perfil Chrome/Chromium nuevo en un user-data-dir,
|
|
# opcionalmente lanzando chromium headless para que la managed policy instale las
|
|
# extensiones forzadas (uBlock, web_proxy). Edita Local State para asignar el nombre
|
|
# legible al perfil.
|
|
|
|
set -euo pipefail
|
|
|
|
create_chrome_profile() {
|
|
# ── defaults ──────────────────────────────────────────────────────────────
|
|
local _udd=""
|
|
local _profile_dir=""
|
|
local _name=""
|
|
local _port=9250
|
|
local _chrome_path=""
|
|
local _no_launch=0
|
|
local _timeout_sec=25
|
|
local _dry_run=0
|
|
|
|
# ── parse args ─────────────────────────────────────────────────────────────
|
|
_usage() {
|
|
cat >&2 <<'EOF'
|
|
Usage: create_chrome_profile --user-data-dir <dir> --profile <dir-name> --name <legible>
|
|
[--port N] [--chrome-path <path>] [--no-launch] [--timeout-sec N] [--dry-run]
|
|
|
|
--user-data-dir Raíz del user-data-dir de Chrome/Chromium (obligatorio).
|
|
--profile Nombre de la carpeta del perfil dentro de user-data-dir, ej: Default,
|
|
"Profile 1", Automation (obligatorio).
|
|
--name Nombre legible que aparece en el selector de perfil, ej: Work, Aurgi
|
|
(obligatorio).
|
|
--port Puerto CDP para el lanzamiento headless. Default: 9250.
|
|
Usar un puerto distinto al 9222 global para no chocar.
|
|
--chrome-path Ruta explícita al binario chromium/chrome. Auto-detecta si se omite.
|
|
--no-launch No lanza chromium. Crea la carpeta y edita Local State offline.
|
|
El perfil no tendrá extensiones instaladas; útil para tests/CRUD.
|
|
--timeout-sec Segundos esperando a que Preferences aparezca tras el lanzamiento.
|
|
Default: 25.
|
|
--dry-run Describe las acciones sin lanzar ni escribir nada.
|
|
|
|
Exit codes:
|
|
0 éxito
|
|
1 error de argumento o validación
|
|
2 lock: ya hay un chromium usando este user-data-dir
|
|
3 timeout esperando a que Preferences se cree
|
|
4 error editando Local State (JSON inválido tras escritura)
|
|
EOF
|
|
return 1
|
|
}
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--user-data-dir) _udd="$2"; shift 2 ;;
|
|
--profile) _profile_dir="$2"; shift 2 ;;
|
|
--name) _name="$2"; shift 2 ;;
|
|
--port) _port="$2"; shift 2 ;;
|
|
--chrome-path) _chrome_path="$2"; shift 2 ;;
|
|
--no-launch) _no_launch=1; shift ;;
|
|
--timeout-sec) _timeout_sec="$2"; shift 2 ;;
|
|
--dry-run) _dry_run=1; shift ;;
|
|
-h|--help) _usage; return 0 ;;
|
|
*) echo "create_chrome_profile: argumento desconocido: $1" >&2; return 1 ;;
|
|
esac
|
|
done
|
|
|
|
# ── validaciones obligatorias ──────────────────────────────────────────────
|
|
if [[ -z "$_udd" ]]; then
|
|
echo "create_chrome_profile: --user-data-dir es obligatorio" >&2
|
|
return 1
|
|
fi
|
|
if [[ -z "$_profile_dir" ]]; then
|
|
echo "create_chrome_profile: --profile es obligatorio" >&2
|
|
return 1
|
|
fi
|
|
if [[ -z "$_name" ]]; then
|
|
echo "create_chrome_profile: --name es obligatorio" >&2
|
|
return 1
|
|
fi
|
|
|
|
local _profile_path="${_udd}/${_profile_dir}"
|
|
local _local_state="${_udd}/Local State"
|
|
local _prefs_file="${_profile_path}/Preferences"
|
|
|
|
# ── guard: lock por user-data-dir ─────────────────────────────────────────
|
|
# Dos procesos chromium no pueden compartir el mismo user-data-dir.
|
|
if [[ $_dry_run -eq 0 && $_no_launch -eq 0 ]]; then
|
|
local _singleton="${_udd}/SingletonLock"
|
|
if [[ -e "$_singleton" ]]; then
|
|
echo "create_chrome_profile: ya hay un chromium corriendo con --user-data-dir=${_udd}" >&2
|
|
echo " (encontrado: ${_singleton})" >&2
|
|
echo " Ciérralo o usa un user-data-dir distinto." >&2
|
|
return 2
|
|
fi
|
|
fi
|
|
|
|
# ── detección del binario chromium ────────────────────────────────────────
|
|
local _bin=""
|
|
if [[ -n "$_chrome_path" ]]; then
|
|
if [[ ! -x "$_chrome_path" ]]; then
|
|
echo "create_chrome_profile: binario no encontrado o no ejecutable: ${_chrome_path}" >&2
|
|
return 1
|
|
fi
|
|
_bin="$_chrome_path"
|
|
elif [[ $_no_launch -eq 0 ]]; then
|
|
for _candidate in chromium chromium-browser google-chrome brave-browser; do
|
|
if command -v "$_candidate" &>/dev/null; then
|
|
_bin="$_candidate"
|
|
break
|
|
fi
|
|
done
|
|
if [[ -z "$_bin" ]]; then
|
|
echo "create_chrome_profile: no se encontró binario chromium en PATH" >&2
|
|
echo " Probados: chromium, chromium-browser, google-chrome, brave-browser" >&2
|
|
echo " Usa --chrome-path o --no-launch." >&2
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
# ── modo dry-run ──────────────────────────────────────────────────────────
|
|
if [[ $_dry_run -eq 1 ]]; then
|
|
echo "=== create_chrome_profile DRY-RUN ===" >&2
|
|
echo " user-data-dir : ${_udd}" >&2
|
|
echo " profile : ${_profile_dir}" >&2
|
|
echo " name : ${_name}" >&2
|
|
if [[ $_no_launch -eq 1 ]]; then
|
|
echo " modo : --no-launch (sin chromium)" >&2
|
|
echo " acciones : mkdir -p ${_profile_path}" >&2
|
|
echo " editar ${_local_state} → info_cache + profiles_order" >&2
|
|
else
|
|
echo " binario : ${_bin}" >&2
|
|
echo " puerto CDP : ${_port}" >&2
|
|
echo " timeout : ${_timeout_sec}s" >&2
|
|
echo " acciones : systemd-run unit=create-prof-<rand> chromium headless" >&2
|
|
echo " poll Preferences hasta ${_timeout_sec}s" >&2
|
|
echo " systemctl --user stop unit" >&2
|
|
echo " editar ${_local_state} → info_cache + profiles_order" >&2
|
|
fi
|
|
printf '{"profile":"%s","name":"%s","launched":false,"preferences_created":false,"dry_run":true}\n' \
|
|
"$_profile_dir" "$_name"
|
|
return 0
|
|
fi
|
|
|
|
# ── crear directorio del perfil ───────────────────────────────────────────
|
|
mkdir -p "$_profile_path"
|
|
|
|
# ── también asegurar que user-data-dir existe ──────────────────────────────
|
|
mkdir -p "$_udd"
|
|
|
|
# ── modo --no-launch: solo estructura + Local State ────────────────────────
|
|
local _launched=false
|
|
local _prefs_created=false
|
|
|
|
if [[ $_no_launch -eq 1 ]]; then
|
|
_update_local_state "$_udd" "$_local_state" "$_profile_dir" "$_name"
|
|
if [[ -f "$_prefs_file" ]]; then
|
|
_prefs_created=true
|
|
fi
|
|
printf '{"profile":"%s","name":"%s","launched":false,"preferences_created":%s}\n' \
|
|
"$_profile_dir" "$_name" "$_prefs_created"
|
|
return 0
|
|
fi
|
|
|
|
# ── lanzar chromium headless vía systemd-run ──────────────────────────────
|
|
# systemd-run --user aísla el proceso del cgroup del agente (evita exit-144).
|
|
# NO se pasa --disable-extensions para que la managed policy instale las
|
|
# extensiones force-listed (uBlock, web_proxy).
|
|
local _rand
|
|
_rand="$(tr -dc 'a-z0-9' </dev/urandom | head -c 8 2>/dev/null || echo "$$")"
|
|
local _unit="create-prof-${_rand}"
|
|
|
|
systemd-run \
|
|
--user \
|
|
--collect \
|
|
--unit="$_unit" \
|
|
--setenv=DISPLAY=:0 \
|
|
--setenv=XAUTHORITY="${HOME}/.Xauthority" \
|
|
"$_bin" \
|
|
"--user-data-dir=${_udd}" \
|
|
"--profile-directory=${_profile_dir}" \
|
|
"--headless=new" \
|
|
"--no-first-run" \
|
|
"--remote-debugging-port=${_port}" \
|
|
"--remote-allow-origins=*" \
|
|
"about:blank" 2>/dev/null || true
|
|
|
|
_launched=true
|
|
|
|
# ── poll: esperar a que Preferences exista ────────────────────────────────
|
|
local _elapsed=0
|
|
while [[ $_elapsed -lt $_timeout_sec ]]; do
|
|
if [[ -f "$_prefs_file" ]]; then
|
|
_prefs_created=true
|
|
break
|
|
fi
|
|
sleep 1
|
|
(( _elapsed++ )) || true
|
|
done
|
|
|
|
# ── detener el unit Y matar TODO el árbol de chromium de este udd ───────────
|
|
# Necesario para poder editar Local State sin que Chrome lo sobreescriba. Ni el
|
|
# `systemctl stop` ni un `pkill -f --user-data-dir=` bastan: los procesos hijos
|
|
# (zygote/gpu/renderer) no repiten el flag --user-data-dir pero sí referencian la
|
|
# ruta del user-data-dir en otros argumentos. Los matamos por PID seleccionando
|
|
# los procesos chromium cuyo cmdline contiene la ruta del udd (seguro: no mata
|
|
# este propio script porque filtramos por '[c]hromium').
|
|
systemctl --user kill -s SIGKILL "$_unit" 2>/dev/null || true
|
|
systemctl --user stop "$_unit" 2>/dev/null || true
|
|
# Matar por PID los procesos cuyo comm es exactamente "chromium" (pgrep -x) y cuyo cmdline
|
|
# contiene la ruta del udd. Usamos pgrep -x para NO auto-matchear grep/pgrep: el path del udd
|
|
# contiene la cadena "chromium" (~/.config/chromium-cdp).
|
|
local _wait=0 _p _pids
|
|
while :; do
|
|
_pids=""
|
|
for _p in $(pgrep -x chromium 2>/dev/null); do
|
|
tr '\0' ' ' < "/proc/$_p/cmdline" 2>/dev/null | grep -qF -- "$_udd" && _pids="$_pids $_p"
|
|
done
|
|
[[ -z "${_pids// }" ]] && break
|
|
# shellcheck disable=SC2086
|
|
kill -TERM $_pids 2>/dev/null || true
|
|
sleep 0.5
|
|
(( _wait++ )) || true
|
|
if [[ $_wait -ge 20 ]]; then
|
|
# shellcheck disable=SC2086
|
|
kill -9 $_pids 2>/dev/null || true
|
|
break
|
|
fi
|
|
done
|
|
rm -f "${_udd}/SingletonLock" 2>/dev/null || true
|
|
|
|
if [[ "$_prefs_created" == false ]]; then
|
|
echo "create_chrome_profile: timeout (${_timeout_sec}s) esperando a que se cree: ${_prefs_file}" >&2
|
|
echo " El directorio del perfil puede existir pero está vacío." >&2
|
|
printf '{"profile":"%s","name":"%s","launched":true,"preferences_created":false,"error":"timeout"}\n' \
|
|
"$_profile_dir" "$_name"
|
|
return 3
|
|
fi
|
|
|
|
# ── editar Local State para asignar nombre legible ────────────────────────
|
|
_update_local_state "$_udd" "$_local_state" "$_profile_dir" "$_name"
|
|
|
|
printf '{"profile":"%s","name":"%s","launched":true,"preferences_created":true}\n' \
|
|
"$_profile_dir" "$_name"
|
|
}
|
|
|
|
# ── helper: editar Local State con python3 ────────────────────────────────────
|
|
# Crea/actualiza info_cache.<profile_dir> con name + is_using_default_name=false
|
|
# y añade profile_dir a profiles_order si no está.
|
|
_update_local_state() {
|
|
local _udd="$1"
|
|
local _local_state="$2"
|
|
local _profile_dir="$3"
|
|
local _name="$4"
|
|
local _today
|
|
_today="$(date +%Y%m%d)"
|
|
|
|
# Si Local State no existe, crear una estructura mínima
|
|
if [[ ! -f "$_local_state" ]]; then
|
|
printf '{"profile":{"info_cache":{},"profiles_order":[]}}\n' > "$_local_state"
|
|
fi
|
|
|
|
# Backup antes de modificar (no sobreescribir el del mismo día)
|
|
local _backup="${_local_state}.bak.${_today}"
|
|
if [[ ! -f "$_backup" ]]; then
|
|
cp "$_local_state" "$_backup"
|
|
fi
|
|
|
|
# Editar con python3
|
|
if ! python3 - "$_local_state" "$_profile_dir" "$_name" <<'PY'; then
|
|
import sys, json
|
|
|
|
ls_path = sys.argv[1]
|
|
prof_dir = sys.argv[2]
|
|
prof_name = sys.argv[3]
|
|
|
|
with open(ls_path, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
|
|
# Asegurar estructura profile
|
|
profile_section = data.setdefault("profile", {})
|
|
info_cache = profile_section.setdefault("info_cache", {})
|
|
|
|
# Crear o actualizar la entrada del perfil en info_cache
|
|
entry = info_cache.setdefault(prof_dir, {})
|
|
entry["name"] = prof_name
|
|
entry["is_using_default_name"] = False
|
|
|
|
# Añadir a profiles_order si no está
|
|
order = profile_section.setdefault("profiles_order", [])
|
|
if prof_dir not in order:
|
|
order.append(prof_dir)
|
|
|
|
with open(ls_path, "w", encoding="utf-8") as f:
|
|
json.dump(data, f, separators=(",", ":"))
|
|
PY
|
|
echo "create_chrome_profile: error editando Local State con python3" >&2
|
|
return 4
|
|
fi
|
|
|
|
# Validar JSON tras escritura
|
|
if ! python3 -c "import json,sys; json.load(open(sys.argv[1]))" "$_local_state" 2>/dev/null; then
|
|
echo "create_chrome_profile: JSON inválido tras escribir Local State; restaurando backup" >&2
|
|
cp "$_backup" "$_local_state"
|
|
return 4
|
|
fi
|
|
}
|
|
|
|
# ── auto-ejecución ────────────────────────────────────────────────────────────
|
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
create_chrome_profile "$@"
|
|
fi
|