diff --git a/bash/functions/browser/install_chromium_proxy_extension.md b/bash/functions/browser/install_chromium_proxy_extension.md new file mode 100644 index 00000000..2a42a5a5 --- /dev/null +++ b/bash/functions/browser/install_chromium_proxy_extension.md @@ -0,0 +1,81 @@ +--- +name: install_chromium_proxy_extension +kind: function +lang: bash +domain: browser +version: 1.0.0 +purity: impure +signature: install_chromium_proxy_extension --ext-dir DIR [--name NAME] [--stable-dir DIR] [--uninstall] +description: "Instala una extension desempaquetada de Chromium en todos los perfiles del usuario de forma persistente, escribiendo un fragmento en /etc/chromium.d/ que el wrapper de Chromium carga en cada arranque. Pensado para distribuir la extension de toggle de proxy de web_proxy sin Web Store, pero sirve para cualquier extension desempaquetada." +tags: [web-proxy, chromium, extension, browser, proxy, install] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +params: + - name: --ext-dir + desc: "Directorio de la extension desempaquetada de origen (debe contener manifest.json). Obligatorio salvo en --uninstall." + - name: --name + desc: "Nombre del fragmento en /etc/chromium.d/ (default web_proxy_ext). Identifica esta instalacion para poder desinstalarla." + - name: --stable-dir + desc: "Ruta estable donde se copia la extension, independiente del repo (default ~/.web_proxy/extension). --load-extension apunta aqui." + - name: --uninstall + desc: "Elimina el fragmento de /etc/chromium.d/ y la copia estable. No requiere --ext-dir." +output: "JSON en stdout: {installed|uninstalled, name, stable_dir, chromiumd, ext_id}. Requiere sudo para escribir en /etc/chromium.d/." +file_path: bash/functions/browser/install_chromium_proxy_extension.sh +--- + +# install_chromium_proxy_extension + +Instala una extension desempaquetada de Chromium en **todos los perfiles** del +usuario, de forma persistente, sin pasar por la Chrome Web Store. + +## Ejemplo + +```bash +# Instalar la extension de toggle de proxy de web_proxy en todos los perfiles +install_chromium_proxy_extension --ext-dir /home/enmanuel/fn_registry/apps/web_proxy/extension + +# Desinstalarla +install_chromium_proxy_extension --uninstall + +# Otra extension, con nombre y ruta estable propios +install_chromium_proxy_extension --ext-dir ~/mis-extensiones/foo --name foo_ext --stable-dir ~/.local/share/foo_ext +``` + +Tras instalar, cierra y vuelve a abrir Chromium: la extension aparece en todos +los perfiles, incluidos los que se creen despues. + +## Cuando usarla + +Cuando necesitas que una extension desempaquetada este presente en todos los +perfiles de Chromium de una maquina (por ejemplo, un toggle de proxy de captura +preconfigurado) y no quieres publicarla en la Web Store ni cargarla a mano en +cada perfil. Es la pieza que hace que `web_proxy` quede "a un clic" en cualquier +ventana de Chromium. + +## Gotchas + +- **Requiere sudo** para escribir en `/etc/chromium.d/`. Ten las credenciales + cacheadas (`sudo -v`) antes de invocarla de forma no interactiva. +- **Solo para el wrapper de Chromium de Debian/Ubuntu** (paquete `chromium`, + no snap ni Google Chrome). El wrapper hace `source /etc/chromium.d/*` en cada + arranque. Comprueba con `head -1 $(command -v chromium)` que es un script. +- **`--enable-remote-extensions` es imprescindible** en estos builds: sin el, + el wrapper anade `--disable-extensions-except` y `--disable-background-networking`, + que deshabilitan toda extension que no venga por `--load-extension`. El + fragmento generado lo incluye; por eso las demas extensiones del usuario + siguen funcionando. +- La extension se carga **desempaquetada** (`--load-extension`), no como `.crx` + firmado. Chromium puede mostrar un aviso de "extensiones en modo desarrollador". + El force-install via managed policy con `.crx` local + `update_url file://` + no funciona con este wrapper (lo bloquea `--disable-extensions-except`). +- El ID de la extension depende de `--stable-dir` (se deriva del path). Si + cambias la ruta estable, el ID cambia. +- No reinicia Chromium: los cambios aplican en el siguiente arranque del + navegador. + +## Capability growth log + +- v1.0.0 (2026-06-02) — version inicial. Instala/desinstala extension global via /etc/chromium.d con --enable-remote-extensions + --load-extension. diff --git a/bash/functions/browser/install_chromium_proxy_extension.sh b/bash/functions/browser/install_chromium_proxy_extension.sh new file mode 100644 index 00000000..c621c59a --- /dev/null +++ b/bash/functions/browser/install_chromium_proxy_extension.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash +# install_chromium_proxy_extension — instala una extension desempaquetada de +# Chromium en TODOS los perfiles del usuario, de forma persistente, escribiendo +# un fragmento en /etc/chromium.d/ que el wrapper de Chromium carga en cada +# arranque. +# +# Por que /etc/chromium.d en vez de managed policy con .crx force_installed: +# el wrapper de Chromium de Debian/Ubuntu (xtradeb y derivados), cuando NO se +# pasa --enable-remote-extensions, anade --disable-extensions-except= y --disable-background-networking. Eso deshabilita +# cualquier extension force_installed por policy y bloquea su update check. La +# via fiable es habilitar --enable-remote-extensions y cargar la extension +# desempaquetada con --load-extension, ambos inyectados de forma global desde +# /etc/chromium.d/, que el wrapper hace `source` en cada lanzamiento. + +install_chromium_proxy_extension() { + local ext_dir="" + local name="web_proxy_ext" + local stable_dir="$HOME/.web_proxy/extension" + local chromiumd="/etc/chromium.d" + local uninstall="no" + + # Permite sudo no interactivo via SUDO_ASKPASS (sudo -A) cuando se ejecuta + # sin terminal (agentes, CI). Con terminal interactivo usa sudo normal. + local SUDO="sudo" + [[ -n "${SUDO_ASKPASS:-}" ]] && SUDO="sudo -A" + + while [[ $# -gt 0 ]]; do + case "$1" in + --ext-dir) ext_dir="$2"; shift 2 ;; + --name) name="$2"; shift 2 ;; + --stable-dir) stable_dir="$2"; shift 2 ;; + --uninstall) uninstall="yes"; shift ;; + *) echo "ERROR: argumento desconocido: $1" >&2; return 1 ;; + esac + done + + if [[ ! -d "$chromiumd" ]]; then + echo "ERROR: $chromiumd no existe. Este Chromium no usa el wrapper con /etc/chromium.d." >&2 + echo " Comprueba 'head -1 \$(command -v chromium)'; si no es un wrapper shell, usa otra via." >&2 + return 1 + fi + + # Desinstalacion: quitar el fragmento global y la copia estable. + if [[ "$uninstall" == "yes" ]]; then + $SUDO rm -f "${chromiumd}/${name}" || { + echo "ERROR: no se pudo eliminar ${chromiumd}/${name} (requiere sudo)." >&2 + return 1 + } + rm -rf "$stable_dir" + printf '{"uninstalled": true, "name": "%s"}\n' "$name" + return 0 + fi + + # Instalacion: validar la extension de origen. + if [[ -z "$ext_dir" || ! -f "${ext_dir}/manifest.json" ]]; then + echo "ERROR: --ext-dir debe apuntar a un directorio con manifest.json." >&2 + return 1 + fi + + # Copiar la extension a una ubicacion estable, independiente del repo, para + # que --load-extension no se rompa si el repo se mueve o se limpia. + mkdir -p "$stable_dir" || { + echo "ERROR: no se pudo crear $stable_dir." >&2 + return 1 + } + # Vaciar destino y copiar el contenido del origen. + rm -rf "${stable_dir:?}/"* 2>/dev/null + cp -r "${ext_dir}/." "$stable_dir/" || { + echo "ERROR: no se pudo copiar la extension a $stable_dir." >&2 + return 1 + } + + # Escribir el fragmento que el wrapper carga en cada arranque. Se hace via + # archivo temporal + sudo cp para no exponer el contenido por una tuberia. + local tmp + tmp="$(mktemp)" + printf 'export CHROMIUM_FLAGS="$CHROMIUM_FLAGS --enable-remote-extensions --load-extension=%s"\n' "$stable_dir" > "$tmp" + if ! $SUDO cp "$tmp" "${chromiumd}/${name}"; then + rm -f "$tmp" + echo "ERROR: no se pudo escribir ${chromiumd}/${name} (requiere sudo)." >&2 + return 1 + fi + $SUDO chmod 0644 "${chromiumd}/${name}" 2>/dev/null + rm -f "$tmp" + + # ID de extension desempaquetada (deterministico: sha256 del path estable). + local ext_id + ext_id="$(python3 - "$stable_dir" <<'PY' 2>/dev/null +import hashlib, sys +h = hashlib.sha256(sys.argv[1].encode()).hexdigest()[:32] +print(''.join(chr(ord('a') + int(c, 16)) for c in h)) +PY +)" + + printf '{"installed": true, "name": "%s", "stable_dir": "%s", "chromiumd": "%s/%s", "ext_id": "%s"}\n' \ + "$name" "$stable_dir" "$chromiumd" "$name" "$ext_id" +} + +# Ejecutar si se llama directamente (fn run / bash ) +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + install_chromium_proxy_extension "$@" +fi diff --git a/bash/functions/browser/launch_chromium_proxy.sh b/bash/functions/browser/launch_chromium_proxy.sh index 1f12e36e..eefee975 100644 --- a/bash/functions/browser/launch_chromium_proxy.sh +++ b/bash/functions/browser/launch_chromium_proxy.sh @@ -7,6 +7,7 @@ launch_chromium_proxy() { local start_url="" local ca_cert="" local extra_args="" + local ext_dir="" # Parsear argumentos while [[ $# -gt 0 ]]; do @@ -19,6 +20,8 @@ launch_chromium_proxy() { start_url="$2"; shift 2 ;; --ca-cert) ca_cert="$2"; shift 2 ;; + --ext) + ext_dir="$2"; shift 2 ;; --extra) extra_args="$2"; shift 2 ;; *) @@ -52,13 +55,24 @@ launch_chromium_proxy() { # Construir argumentos del navegador local args=( - "--proxy-server=${proxy_url}" "--user-data-dir=${profile_dir}" - "--proxy-bypass-list=<-loopback>" "--no-first-run" "--no-default-browser-check" ) + # Proxy fijo opcional. Con "--proxy none" (o vacio) no se fija proxy en el + # cmdline: util cuando una extension de proxy gestiona la conexion (toggle). + if [[ -n "$proxy_url" && "$proxy_url" != "none" ]]; then + args+=("--proxy-server=${proxy_url}" "--proxy-bypass-list=<-loopback>") + fi + + # Cargar una extension desempaquetada (--load-extension). Funciona en + # Chromium (no en Chrome stable 138+). Para persistencia en todos los + # perfiles se usa managed policy en su lugar. + if [[ -n "$ext_dir" ]]; then + args+=("--load-extension=${ext_dir}") + fi + # Manejo de certificados TLS if [[ -n "$ca_cert" ]]; then # El usuario instalo el CA en el perfil; no ignorar errores de certificado.