# Salvaguardas del instalador

Los objetos sobre los que install no construye, los valores que no mete en una orden de RouterOS y cómo demuestra uninstall que no dejó nada atrás.

Source: https://docs.jmrp.io/mikroscope/es/security/installer/

El instalador escribe en un router que no configuró él, por la propia sesión ssh de administrador
del operador, así que no hay ninguna frontera de privilegios entre un error y el router. Lo que hace
sus veces es un conjunto de rechazos: dónde se detiene `install` antes de escribir, qué no toca, qué
trata como un fallo, y cómo `uninstall` demuestra que ha terminado en vez de limitarse a decirlo.

## Listado antes de escribir

`install` no escribe nada hasta haber impreso cada orden que ejecutaría, con su texto exacto de
RouterOS. En este orden:

1. ejecuta `doctor`, la comprobación previa de solo lectura, en una sola conexión ssh, salvo que se
   dé `--no-doctor`; un requisito que falte lo detiene con
   `N prerequisite(s) missing; nothing was written`. Una línea `WARN` se imprime y no lo detiene.
   Con `--no-doctor`, sin `--arch` y sin `--remote-image`, una conexión de una sola lectura toma en
   su lugar la arquitectura del router, y la salida lo dice;
2. construye o carga la imagen de la arquitectura que informó el router (`--agent-tar` tiene que
   coincidir con ella; `--remote-image` deja la elección a RouterOS);
3. imprime el listado, con el token enmascarado como `value="(token)"`;
4. pregunta `write the objects above to the router? [y/N]`, salvo que se dé `--yes`; cualquier cosa
   que no sea `y` o `Y` lo detiene con `not confirmed; nothing written`;
5. solo entonces escribe, el manifiesto de la instalación el primero.

`plan`, `install --dry-run` y `upgrade --dry-run` se detienen después del listado y no abren
ninguna conexión.

`upgrade` no ejecuta `doctor`. En una sola conexión lee la forma con la que se hizo la instalación
(su manifiesto, o sus objetos etiquetados para una instalación que no lo tiene), si están todos los
pasos, la arquitectura del router y, con `--remote-image`, la comprobación de credencial de registro
de doctor, que imprime; cuando la forma que lee cambia el plan que daban las opciones, una segunda
conexión pregunta por los pasos de ese plan. Rechaza un router sin instalación (`nothing to upgrade: run install first`)
y una instalación cuya envlist tiene un `TOKEN` si no recibe `--token`. Lista el manifiesto de la
instalación, que escribe siempre el primero, y el paso del contenedor; cualquier otro paso de la
instalación que el router ya no tenga se lista después y se crea antes del contenedor. Después hace
la misma pregunta. Con `y` quita el contenedor, la envlist y la imagen y los vuelve a
escribir, la envlist a partir de las opciones dadas a `upgrade`.

**Lo que `install` escribe en tu router**

- el manifiesto de la instalación, un fichero `mikroscope/<name>.manifest.txt` en el disco de la instalación que lista las opciones y cada objeto de abajo
- una veth
- una dirección
- una pertenencia a lista de interfaces, salvo con `--iface-list none`
- una entrada de address-list, salvo con `--addr-list none`
- una envlist
- el tar de la imagen, que se borra en cuanto el contenedor está extraído, salvo que `--remote-image` haga que el router se la baje
- el contenedor, y su raíz `mikroscope/<name>` en el mismo disco

Cada objeto lleva el comentario `mikroscope:<name> (managed by mikroscope)`

`mikroscope plan` imprime cada orden antes de escribir nada.

`uninstall` elimina por etiqueta exacta más identidad, nunca por patrón, y falla nombrando el paso si queda algo.

## Objetos ajenos

`doctor` nombra los objetos con los que chocaría una instalación antes de escribir nada: una veth,
una envlist o un nombre de contenedor que no son de esta instalación, un fichero en la ruta del
manifiesto de la instalación que no es su manifiesto, y una ruta que se solapa con la /30. Después, antes de escribir, `install` hace al router tres preguntas sobre cada paso en una
sola conexión ssh: ¿está nuestro objeto?, ¿existe algo con el mismo efecto?, ¿lleva nuestra
etiqueta? Un objeto que existe pero no lleva la etiqueta de mikroscope detiene `install`, nombrando
el paso:

```text
veth interface veth-mikroscope exists on the router and was not created by mikroscope (no ownership tag); pick another --name/--veth/--subnet, or remove it by hand if it is yours; nothing was written
```

Lo que cuenta como «el mismo efecto» es la identidad del objeto, no solo su nombre:

| Paso                                  | Choca con cualquier existente                                                                                           | Es nuestro cuando lleva                                 |
| ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
| manifiesto de la instalación | un fichero en `mikroscope/<name>.manifest.txt` en el disco de la instalación | su línea `tag=` con la etiqueta |
| veth                                  | `/interface/veth` con el mismo nombre                                                                                   | la etiqueta en `comment`                                |
| dirección del router                  | `/ip/address` en esa veth                                                                                               | la etiqueta en `comment`                                |
| pertenencia a la lista de interfaces  | miembro con esa interfaz en esa lista                                                                                   | la etiqueta en `comment`                                |
| pertenencia a la lista de direcciones | entrada con la /30 en esa lista                                                                                         | la etiqueta en `comment`                                |
| dst-nat de expose                     | regla `dstnat` con esa dirección de destino, puerto y protocolo                                                         | la etiqueta en `comment`                                |
| accept en forward de expose           | regla `forward` con la dirección del contenedor, ese puerto y ese protocolo                                             | la etiqueta en `comment`                                |
| contenedor | un contenedor en la misma veth, una envlist llamada `<name>-env`, un fichero en la ruta de la imagen o un contenedor llamado como `--container-name` | un contenedor con la etiqueta; una envlist con la marca |

Un paso que ya es nuestro se salta, así que ejecutar `install` dos veces no crea nada la segunda vez.

`install` tiene todas las respuestas antes de su primera escritura, así que un objeto ajeno en
cualquier paso lo detiene sin haber escrito nada, también con `--no-doctor`. `uninstall` nunca toca
el objeto ajeno.

### Objetos sin etiqueta

Ni `/container/envs` ni `/file` tienen campo de comentario, así que la instalación los firma de otra
manera. El manifiesto lleva la etiqueta en una línea `tag=` y es de esta instalación solo mientras
la lleve. En el paso del contenedor, la primera entrada que se escribe en la envlist es `MIKROSCOPE_TAG` con la etiqueta
exacta, y el fichero de imagen cuenta como nuestro solo mientras exista esa marca. Una envlist ajena
con el mismo nombre, un fichero ajeno en la ruta de la imagen o la envlist de otro contenedor son,
por tanto, ajenos, y detienen `install`. Los restos de una instalación anterior de mikroscope — una
envlist bajo nuestra marca sin contenedor — son nuestros para sustituirlos, e `install` los limpia
antes de escribir los nuevos.

## Selectores exactos

Todo objeto que crea `install` lleva el comentario `mikroscope:<name> (managed by mikroscope)`, al
pie de la letra. Todo objeto de red se elimina por ese comentario exacto junto con la identidad
que usó su comprobación — `comment="…"`, nunca una coincidencia por patrón. El contenedor se elimina
solo por el comentario, y la envlist y la imagen, que no llevan comentario, por `list="<name>-env"`
y el nombre exacto del fichero de imagen, solo mientras exista la entrada de marca con la etiqueta
exacta; el manifiesto, la raíz del contenedor y el directorio `mikroscope`, por sus rutas exactas,
que derivan de `--name` y `--disk`. Así que `uninstall` no puede alcanzar una instalación hecha a mano, ni nada cuyo comentario
o nombre comparta una subcadena con el nombre del contenedor.

Todo `find` pone entre comillas cada valor no numérico salvo los enumerados `chain` y `action`. Sin
comillas, una dirección o un puerto se interpretan como valores tipados y la comparación con el
valor guardado sale vacía. Una palabra suelta como `tcp` se lee como el nombre de una variable,
cuyo valor sin definir es vacío, así que `find chain=dstnat protocol=tcp` no coincide con ninguna
regla mientras que `protocol="tcp"` coincide con todas las de TCP.

## Errores de escritura

Una escritura de RouterOS no imprime nada cuando tiene éxito, y por ssh informa de los errores como
texto, con estado de salida 0 o 1, abandonando el resto de una línea unida con `;`. Así que
`install` trata cualquier salida de una escritura como un fallo
(`create <step>: router said "…"`) y se detiene ahí. Cuando ssh sale con un estado distinto de
cero, el error empieza por lo que imprimió RouterOS, así que sus palabras sobreviven donde solo se
muestra la primera línea de un error, como en las líneas `skip` de `uninstall`.

El tar de la imagen se sube con scp antes de que se ejecute el paso del contenedor, y antes de que
exista la marca. Si ese paso falla después, `install` borra el fichero subido
(`undo  removed the uploaded …`); de lo contrario contaría como fichero ajeno en cada intento
posterior. El tar se queda cuando ya lo tiene un contenedor de esta instalación — el paso se detuvo
en su espera de la extracción, y RouterOS puede seguir extrayéndolo (`keep  …`) — y `uninstall`
retira los dos juntos. El plazo de ssh para una orden es de 3 minutos, o el `--extract-timeout` y un
minuto más cuando eso es mayor, así que la espera de la extracción termina con su propio mensaje y
no con ssh interrumpido.

`uninstall` lee la salida de la misma manera en sentido contrario: una eliminación que imprimió algo
se informa como `skip`, no como `gone`.

## Límites de entrada

Toda opción que llega a una orden de RouterOS se interpola en ella tal cual. No hay privilegio que
escalar — la orden se ejecuta como tu administrador — pero unas comillas o un punto y coma
convertirían un error claro en un confuso error de sintaxis de RouterOS, o un selector en algo más
amplio de lo pretendido. Por eso cada valor se comprueba antes de la primera conexión, y uno fuera
de estos límites detiene la orden con la regla que ha incumplido:

| Opción                                  | Se acepta                                                                                       |
| --------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `--name`                                | `^[A-Za-z0-9][A-Za-z0-9_.-]{0,31}$`                                                             |
| `--veth` | `^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$` |
| `--iface-list`, `--addr-list` | `^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$`, o `none` para no unirse a ninguna lista; `--iface-list` rechaza las listas integradas de RouterOS `all`, `dynamic` y `static`, que no admiten miembros |
| `--container-name` | `^[A-Za-z0-9][A-Za-z0-9_.-]{0,31}$`, o vacío para el nombre que ponga RouterOS |
| `--disk`                                | `^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$`, o vacío para la flash interna; `--ephemeral` fuerza `tmpfs` |
| `--arch` | `arm64`, `arm`, `amd64` o `auto` |
| `--token`                               | `^[A-Za-z0-9_.-]{0,128}$`                                                                       |
| `--subnet`                              | una /30 IPv4, escrita como su dirección de red                                                  |
| `--port`                                | 1–65535                                                                                         |
| `--rate`                                | 1–100 Hz                                                                                        |
| `--buffer`                              | 10–3600 s                                                                                       |
| `--memory-max`                          | `^\d{1,6}[KMG]?$`                                                                               |
| `--mem-limit-mb`                        | 8–1024                                                                                          |
| `--floor-hz`                            | 0–1000                                                                                          |
| `--capture-mb`                          | 0–256                                                                                           |
| `--restart-max-count` | 0–100 |
| `--restart-interval` | `^\d{1,4}[smh]$` |
| `--start-on-boot` | `auto`, `yes` o `no` |
| `--extract-timeout` | `^\d{1,4}[smh]$`, 10–600 s |
| `--expose` | necesita `--lan-address` como dirección IPv4; `install`, `upgrade` y `plan` necesitan además un token no vacío |
| `--triggers`                            | la propia lista de condiciones del agente, interpretada por `agent.ParseTriggers`               |
| `--remote-image`                        | una referencia de registro: `owner/name:1.0.0`, con host o sin él, sin comillas, espacios ni punto y coma  |
| `--ssh-option` | una clave entre `StrictHostKeyChecking`, `UserKnownHostsFile`, `ConnectTimeout`, `HostKeyAlgorithms`, `PubkeyAcceptedAlgorithms`, `IdentitiesOnly` y `ServerAliveInterval`, y un valor que cumpla `^[A-Za-z0-9_./~+:-]{1,256}$` |

`--ssh-option` no llega a RouterOS sino a las líneas de órdenes de ssh y scp, y rige la misma regla:
ninguna clave que ejecute una orden o lea un fichero como configuración (`ProxyCommand`,
`LocalCommand`, `Include`), para que un valor en un fichero de entorno no pueda convertirse en
ejecución de órdenes. Sus opciones van antes de las propias de la CLI,
`-o BatchMode=yes -o ConnectTimeout=15`, porque ssh se queda con el primer valor que lee de cada
opción.

No hay excepción. `--triggers` la comprueba la misma puerta que el resto: `Finish` se la pasa a
`agent.ParseTriggers`, el propio intérprete del agente, que es la autoridad sobre lo que significa
una condición. Una condición desconocida, un umbral mal formado, unas comillas o un punto y coma
hacen fallar el verbo con estado de salida 2 antes de la primera conexión, y no se escribe nada.

El agente vuelve a interpretar `TRIGGERS` al arrancar, porque la envlist se puede editar a mano en
el router. Ante un valor que no puede interpretar sale con un estado distinto de cero y una línea
`mikroscope-agent: bad configuration: …` en el log del router; con la política on-failure, RouterOS
lo reintenta hasta `--restart-max-count` veces.

## Comprobaciones de la imagen

Dos de las cuatro vías de instalación entregan al router algo que la CLI no ha construido, y cada
una tiene su propia comprobación antes de que se escriba nada.

- **`--agent-tar <fichero>`**, el tar de imagen que publica la release, se lee e inspecciona primero
  en tu máquina. Tiene que ser un tar de tipo docker-save con exactamente una imagen de una sola
  capa cuyo entrypoint sea `/mikroscope-agent`, y su arquitectura tiene que coincidir con la del
  router (que lee `doctor`) o con un `--arch` explícito; si no, el verbo se detiene, y ante una
  discrepancia nombra el recurso que hay que descargar (`--agent-tar … is a linux/arm64 image and
  the router is arm: download the mikroscope-agent-armv5.tar asset instead`, con `armv7` para
  `--goarm 7`). Esa comprobación dice que el tar es una imagen del agente de mikroscope de la
  arquitectura correcta. No dice que sea el tar que publicó la release: verifícalo contra
  `checksums.txt` de la release, y su firma cosign si la usas, antes de pasarlo.
- **`--remote-image <referencia>`** hace que el router se descargue la imagen él mismo, así que no
  se sube nada y ningún tar acaba en el dispositivo. La referencia se contrasta con un patrón de
  referencia de registro antes de llegar a la línea de órdenes, porque RouterOS la recibe dentro de
  una cadena entrecomillada en una línea unida por `;`. Va entera en `remote-image=`, con el host
  del registro incluido: una referencia sin host, o con un alias de Docker Hub, pasa a ser
  `registry-1.docker.io/…`, y cualquier otro host se conserva. Después el router necesita alcanzar
  ese registro por su propia red. **mikroscope nunca escribe `/container/config`**, el ajuste global
  que guarda el `registry-url` del equipo y su único usuario y contraseña de registro, y no necesita
  nada de él: el host dentro de `remote-image=` manda sobre `registry-url` ([Ajustes del
  registro](https://docs.jmrp.io/mikroscope/es/install/routes/#nada-que-ajustar-en-el-router)). Confiar en la imagen es
  confiar en ese registro: nada en la CLI verifica lo que el router se descarga. Cuando hay un
  usuario puesto, RouterOS lo presenta al registro que nombra `registry-url`, y una credencial
  pensada para otro registro puede terminar en `auth error` la descarga de una imagen pública. `doctor` avisa cuando hay un usuario puesto y `registry-url` está vacío o nombra
  un host distinto del que viene la imagen (`WARN no registry credential meant for another
  registry`), y `upgrade` imprime la misma comprobación antes de retirar nada; los dos leen
  `registry-url` y solo si hay un usuario puesto, nunca el nombre. La contraseña no se puede leer en
  absoluto. `--agent-tar` no descarga de ningún registro, así que no se envía ninguna
  credencial de registro.

## Scripts y tokens

`plan --rsc` escribe la instalación como un script de RouterOS para un router al que solo llegas por
WinBox o WebFig. Lleva las mismas órdenes que ejecuta `install`, en el mismo orden, con las mismas
etiquetas y el mismo manifiesto de la instalación, así que `uninstall` retira una instalación hecha
por script tan por completo como una propia. Se ejecuta como un solo bloque cuyas guardas lo
detienen antes de su primera escritura en un router que no puede recibirlo: por debajo de RouterOS
7.24, sin el paquete container o sin device-mode, sin el tar en la vía del tar, o con una veth, una
envlist, un contenedor de `--container-name` o un fichero en la ruta del manifiesto con los nombres
de la instalación que no son de mikroscope. Una envlist sin la marca de mikroscope recibiría si no
la marca, y `uninstall` retiraría después con ella las entradas que guarda. Y, cuando `--token` o
`MIKROSCOPE_TOKEN` está fijado, la línea de la envlist lleva el token en claro, porque el router lo necesita. El propio script lo dice en su cabecera. Trata el
fichero como tratas el token: no lo subas a un repositorio, no lo pegues donde quede registrado y
bórralo de los Files del router después del `/import`. Sin token no guarda ningún secreto, solo el
plan. Pégalo solo en el indicador `] >`: un script pegado mientras RouterOS pregunta por la licencia
pierde sus primeras líneas. [Script de RouterOS](https://docs.jmrp.io/mikroscope/es/install/script/) y el [Generador de
scripts](https://docs.jmrp.io/mikroscope/es/install/generator/) muestran las dos formas de ejecutarlo.

`plan`, `install --dry-run` y `upgrade --dry-run` enmascaran el token en lo que imprimen por el
terminal (`value="(token)"`); `--rsc` no puede, porque el script tiene que ejecutarse.

Las órdenes de la propia CLI nunca ponen el token en una línea de órdenes de esta máquina: la orden
de RouterOS que lo escribe le llega a ssh por su entrada estándar, así que no aparece en la tabla de
procesos mientras se ejecuta ssh. La CLI lo lee de `--token` o de `MIKROSCOPE_TOKEN`; la variable lo
mantiene también fuera de la línea de órdenes de la propia CLI.

## Verificación de uninstall

`uninstall` retira todo lo que creó la instalación, y nada más.

1. Lee el manifiesto de la instalación, `mikroscope/<name>.manifest.txt`, en la conexión que lee la
   forma de la instalación, y toma de él la forma con que se hizo; rechaza una opción que la
   contradiga, nombrando los dos valores. Una instalación hecha antes de que existiera el manifiesto
   no tiene ninguno, y su forma se lee de sus objetos etiquetados.
2. Ejecuta cada eliminación de la más reciente a la más antigua, ignorando lo que ya no está. El
   paso del contenedor, una vez retirado el contenedor de esta instalación, retira la raíz del
   contenedor `mikroscope/<name>` si RouterOS la dejó.
3. Elimina, por la etiqueta exacta y nada más amplio, cualquier otro objeto que lleve la etiqueta de
   la instalación en los menús donde escribe una instalación, y en cualquier otro menú que liste el
   manifiesto.
4. Elimina el manifiesto el último: primero la raíz del contenedor, si queda una, ningún contenedor
   la usa y el manifiesto es de esta instalación, después el manifiesto, y después el directorio
   `mikroscope` cuando no queda nada más en él. Un `/file/remove` de un directorio se lleva todo lo
   que hay debajo, así que un directorio con cualquier otra cosa dentro se queda. Una ruta solo se
   borra con la palabra del contenedor etiquetado o del manifiesto; sin ninguno de los dos, una raíz
   que ningún contenedor usa se informa, no se borra.
5. Pregunta al router, en una sola conexión, cuántos objetos de cada paso siguen ahí, cuántos llevan
   la etiqueta en cada menú además de los que contaron esos pasos, y si está alguna ruta que lista
   el manifiesto; imprime una línea por paso con la cuenta y falla nombrando cada paso, menú o ruta
   que quede (`uninstall left objects behind: …`). Una eliminación que no imprimió nada no es una
   prueba; la cuenta sí.

`status` hace esa misma cuenta de forma independiente, en la conexión que lee la forma de la
instalación.

Lo que `uninstall` nunca toca: device-mode, el paquete `container`, `/container/config`, ni ninguna
lista, disco, regla o fichero que el router ya tuviera antes de la instalación, incluido un
directorio `mikroscope` con otros ficheros dentro. Un directorio `mikroscope` vacío en el disco de
la instalación se borra aunque estuviera antes de la instalación: nada en él dice de quién es, y
borrarlo no se lleva nada consigo.

El paso del contenedor es el lento, y el orden dentro de él es lo que mantiene honesta la cuenta:

- el contenedor se detiene (con guarda, porque detener un contenedor detenido es un error), y
  `uninstall` espera, hasta 30 s, a que RouterOS lo informe ni en marcha ni deteniéndose: RouterOS
  se niega a eliminar un contenedor que aún se está deteniendo (`cannot remove running`), y quita la
  marca de en marcha antes de que se haya detenido;
- `/container/remove` vuelve antes de que el contenedor haya desaparecido, y un `/file/remove` de la
  imagen lanzado entretanto no hace nada, en silencio, así que espera hasta 20 s a que el contenedor
  desaparezca, y luego reintenta la eliminación del fichero durante hasta 15 s;
- se va el resto de la envlist, y la marca se va la última, solo cuando el fichero ya no está.

Si la eliminación del fichero no surte efecto, la marca se queda, la cuenta sigue incluyendo la
envlist y el fichero, y `uninstall` lo dice en vez de informar de que está limpio.

El [laboratorio virtual de
RouterOS](https://docs.jmrp.io/mikroscope/es/reference/testing/#el-laboratorio-virtual-de-routeros) lo comprueba para
cada vía de instalación, y para una instalación sin manifiesto: después de `uninstall`, el `/export`
del router es igual al tomado antes de la instalación, y `/file` no lista ninguna ruta de
mikroscope.

## Véase también

- [Instalar con la CLI](https://docs.jmrp.io/mikroscope/es/install/): las órdenes, y lo que comprueba `doctor` antes de
  todo esto.
- [Almacenamiento y ajustes del contenedor](https://docs.jmrp.io/mikroscope/es/install/layout/): cada objeto que crea `install`, y
  la opción que lo mueve.
- [Exponer en la LAN](https://docs.jmrp.io/mikroscope/es/security/expose/): las dos reglas de cortafuegos opcionales y
  sus selectores.
- [CLI](https://docs.jmrp.io/mikroscope/es/reference/cli/): cada opción con su valor por defecto y su variable.
