Rebelión en la granja — investigación y laboratorio de dispositivos
Investigación sobre granjas de dispositivos y automatización completa para levantar un laboratorio Android sobre un chasis de blades Debian. Conclusión que ordena el repositorio: el cuello de botella nunca fue el cómputo sino la identidad. Desde que la atestación de dispositivo pasó de estadística a criptográfica, un servidor no puede hacerse pasar por un teléfono — le falta una clave grabada en fábrica que ningún software produce. Contenido: - 01_investigacion/ — capa de identidad (verificación telefónica, eSIM, reputación de IP, atestación por hardware, correlación entre cuentas), mercado y players, stack técnico, marco legal en la UE - 02_device_lab/ansible/ — Ansible para 16 blades: preflight de solo lectura que identifica el hardware por DMI, despliegue de redroid en Docker y control de flota por adb. Solo ansible.builtin, sin colecciones externas - docs/ — requisitos, costes y repaso de seguridad con 11 hallazgos - 03_charla/ — guion para HackMadrid / Hackmeeting València Estado: la automatización está escrita pero SIN ejecutar contra hardware. YAML validado, plantillas renderizadas en ambos modos de red y fleet.sh comprobado con bash -n. El modelo de blade sigue sin confirmar.
This commit is contained in:
commit
74bfbe9437
42 changed files with 2989 additions and 0 deletions
38
02_device_lab/ansible/roles/base/tasks/main.yml
Normal file
38
02_device_lab/ansible/roles/base/tasks/main.yml
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
- name: Actualizar índice de apt
|
||||
ansible.builtin.apt:
|
||||
update_cache: true
|
||||
cache_valid_time: 3600
|
||||
|
||||
- name: Paquetes base
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- ca-certificates
|
||||
- curl
|
||||
- gnupg
|
||||
- htop
|
||||
- iproute2
|
||||
- python3
|
||||
- rsync
|
||||
state: present
|
||||
|
||||
- name: Leer zona horaria actual
|
||||
ansible.builtin.command:
|
||||
cmd: timedatectl show -p Timezone --value
|
||||
register: base_tz
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Fijar zona horaria
|
||||
ansible.builtin.command:
|
||||
cmd: "timedatectl set-timezone {{ granja_timezone | default('Europe/Madrid') }}"
|
||||
when: base_tz.stdout | default('') != (granja_timezone | default('Europe/Madrid'))
|
||||
changed_when: true
|
||||
failed_when: false
|
||||
|
||||
- name: Sincronización horaria activa
|
||||
ansible.builtin.systemd:
|
||||
name: systemd-timesyncd
|
||||
enabled: true
|
||||
state: started
|
||||
failed_when: false
|
||||
84
02_device_lab/ansible/roles/binder/tasks/main.yml
Normal file
84
02_device_lab/ansible/roles/binder/tasks/main.yml
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
---
|
||||
# El punto donde esto se atasca en Debian.
|
||||
#
|
||||
# Debian 12 compila binder como módulo (CONFIG_ANDROID_BINDER_IPC=m) pero SIN
|
||||
# binderfs (CONFIG_ANDROID_BINDERFS no está activado). Sin binderfs, redroid no
|
||||
# puede crear los devices por sí mismo: hay que cargarlos a mano y con los TRES
|
||||
# nombres que Android espera — binder, hwbinder, vndbinder.
|
||||
#
|
||||
# Los contenedores comparten este binder del kernel anfitrión. Si no está, no
|
||||
# arranca ni una instancia.
|
||||
|
||||
- name: Comprobar que el kernel trae binder_linux
|
||||
ansible.builtin.stat:
|
||||
path: "/lib/modules/{{ ansible_kernel }}/kernel/drivers/android/binder_linux.ko"
|
||||
register: binder_ko
|
||||
|
||||
- name: Abortar si no hay módulo binder
|
||||
ansible.builtin.assert:
|
||||
that: binder_ko.stat.exists
|
||||
fail_msg: >-
|
||||
Este kernel ({{ ansible_kernel }}) no trae binder_linux.ko.
|
||||
Sin binder no hay redroid. Salidas: instalar el kernel estándar de Debian,
|
||||
o el módulo DKMS de Waydroid (binder_linux-dkms), que además aporta binderfs.
|
||||
|
||||
- name: Cargar binder_linux en cada arranque
|
||||
ansible.builtin.copy:
|
||||
content: "binder_linux\n"
|
||||
dest: /etc/modules-load.d/redroid.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
|
||||
- name: Fijar los tres devices como parámetro del módulo
|
||||
ansible.builtin.copy:
|
||||
content: "options binder_linux devices=binder,hwbinder,vndbinder\n"
|
||||
dest: /etc/modprobe.d/redroid.conf
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
|
||||
- name: Ver qué devices binder existen ya
|
||||
ansible.builtin.stat:
|
||||
path: "/dev/{{ item }}"
|
||||
loop: [binder, hwbinder, vndbinder]
|
||||
register: binder_devs_before
|
||||
|
||||
- name: ¿Falta alguno?
|
||||
ansible.builtin.set_fact:
|
||||
binder_needs_load: "{{ binder_devs_before.results | rejectattr('stat.exists') | list | length > 0 }}"
|
||||
|
||||
# Si el módulo ya está cargado pero con los parámetros equivocados, modprobe no
|
||||
# lo recarga solo. Hay que descargarlo primero.
|
||||
- name: Descargar binder_linux si está cargado mal
|
||||
ansible.builtin.command:
|
||||
cmd: modprobe -r binder_linux
|
||||
when: binder_needs_load
|
||||
register: binder_rmmod
|
||||
failed_when: false
|
||||
changed_when: binder_rmmod.rc == 0
|
||||
|
||||
- name: Cargar binder_linux con los tres devices
|
||||
ansible.builtin.command:
|
||||
cmd: modprobe binder_linux devices=binder,hwbinder,vndbinder
|
||||
when: binder_needs_load
|
||||
register: binder_modprobe
|
||||
failed_when: false
|
||||
changed_when: binder_modprobe.rc == 0
|
||||
|
||||
- name: Verificar los devices binder
|
||||
ansible.builtin.stat:
|
||||
path: "/dev/{{ item }}"
|
||||
loop: [binder, hwbinder, vndbinder]
|
||||
register: binder_devs_after
|
||||
|
||||
- name: Confirmar que los tres devices están
|
||||
ansible.builtin.assert:
|
||||
that: binder_devs_after.results | rejectattr('stat.exists') | list | length == 0
|
||||
fail_msg: >-
|
||||
Faltan devices binder tras el modprobe:
|
||||
{{ binder_devs_after.results | rejectattr('stat.exists') | map(attribute='item') | join(', ') }}.
|
||||
Causa habitual: el módulo estaba cargado y en uso, así que no se pudo recargar
|
||||
con los parámetros nuevos. La configuración persistente YA está escrita, así que
|
||||
un reinicio del blade lo arregla. Reinicia y relanza este playbook.
|
||||
success_msg: "binder, hwbinder y vndbinder presentes."
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
---
|
||||
coordinator_dir: /opt/granja
|
||||
61
02_device_lab/ansible/roles/coordinator/tasks/main.yml
Normal file
61
02_device_lab/ansible/roles/coordinator/tasks/main.yml
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
---
|
||||
# El coordinador es el único punto desde el que se habla con toda la flota.
|
||||
# No corre instancias Android: corre adb y los scripts.
|
||||
#
|
||||
# Importante en modo macvlan: un anfitrión NO puede hablar con sus propios
|
||||
# contenedores macvlan. Por eso adb vive aquí, en un blade distinto — desde
|
||||
# fuera sí se llega. Ver el README.
|
||||
|
||||
- name: Instalar herramientas de control
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- adb
|
||||
- jq
|
||||
state: present
|
||||
|
||||
- name: Crear directorios del coordinador
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
loop:
|
||||
- "{{ coordinator_dir }}"
|
||||
- "{{ coordinator_dir }}/apks"
|
||||
|
||||
- name: Escribir el listado de dispositivos de la flota
|
||||
ansible.builtin.template:
|
||||
src: devices.txt.j2
|
||||
dest: "{{ coordinator_dir }}/devices.txt"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
|
||||
- name: Instalar el script de flota
|
||||
ansible.builtin.template:
|
||||
src: fleet.sh.j2
|
||||
dest: "{{ coordinator_dir }}/fleet.sh"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Enlazar fleet en el PATH
|
||||
ansible.builtin.file:
|
||||
src: "{{ coordinator_dir }}/fleet.sh"
|
||||
dest: /usr/local/bin/fleet
|
||||
state: link
|
||||
|
||||
- name: Contar dispositivos de la flota
|
||||
ansible.builtin.command:
|
||||
cmd: "grep -cv '^#' {{ coordinator_dir }}/devices.txt"
|
||||
register: coordinator_count
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Resumen
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "Coordinador listo en {{ inventory_hostname }}."
|
||||
- "Flota declarada: {{ coordinator_count.stdout | default('0') | trim }} dispositivos."
|
||||
- "Uso: fleet connect · fleet list · fleet install <apk> · fleet shell '<cmd>'"
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# GENERADO POR ANSIBLE — no editar a mano.
|
||||
# Una dirección adb por línea: <host>:<puerto>
|
||||
# Regenerar con: ansible-playbook playbooks/coordinator.yml
|
||||
{% for h in groups['redroid_nodes'] | sort %}
|
||||
{% set hv = hostvars[h] %}
|
||||
{% set count = hv.redroid_instance_count | default(
|
||||
[ (((hv.ansible_memtotal_mb | int) - (redroid_host_reserve_mb | int))
|
||||
/ (redroid_mem_mb | int)) | int,
|
||||
redroid_max_instances | int ] | min ) %}
|
||||
# --- {{ h }} ({{ count }} instancias) ---
|
||||
{% for i in range(count | int) %}
|
||||
{% if (hv.redroid_network_mode | default(redroid_network_mode)) == 'macvlan' %}
|
||||
{{ hv.redroid_macvlan_ip_prefix | default(redroid_macvlan_ip_prefix) }}{{ (hv.redroid_macvlan_ip_offset | int) + i }}:5555
|
||||
{% else %}
|
||||
{{ hv.ansible_host | default(h) }}:{{ (hv.redroid_adb_base_port | default(redroid_adb_base_port) | int) + i }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
135
02_device_lab/ansible/roles/coordinator/templates/fleet.sh.j2
Normal file
135
02_device_lab/ansible/roles/coordinator/templates/fleet.sh.j2
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
#!/usr/bin/env bash
|
||||
# GENERADO POR ANSIBLE — no editar a mano.
|
||||
# Fuente: roles/coordinator/templates/fleet.sh.j2
|
||||
#
|
||||
# Control de la flota Android de la granja desde un único sitio.
|
||||
set -euo pipefail
|
||||
|
||||
DEVICES_FILE="{{ coordinator_dir }}/devices.txt"
|
||||
APK_DIR="{{ coordinator_dir }}/apks"
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
fleet — control de la flota Android
|
||||
|
||||
fleet connect conecta adb a toda la flota
|
||||
fleet disconnect desconecta todo
|
||||
fleet list dispositivos conectados y su estado
|
||||
fleet install <apk> instala un APK en toda la flota
|
||||
fleet uninstall <pkg> desinstala un paquete de toda la flota
|
||||
fleet shell '<cmd>' ejecuta un comando de shell en toda la flota
|
||||
fleet ip IP de cada dispositivo (útil para el caso de dos APKs)
|
||||
fleet reboot reinicia toda la flota
|
||||
|
||||
El listado de dispositivos sale de devices.txt, que genera Ansible.
|
||||
USAGE
|
||||
}
|
||||
|
||||
declared_devices() {
|
||||
grep -v '^#' "$DEVICES_FILE" | grep -v '^[[:space:]]*$'
|
||||
}
|
||||
|
||||
connected_devices() {
|
||||
adb devices | awk '/\tdevice$/ {print $1}'
|
||||
}
|
||||
|
||||
require_connected() {
|
||||
local n
|
||||
n=$(connected_devices | wc -l)
|
||||
if [[ "$n" -eq 0 ]]; then
|
||||
echo "No hay dispositivos conectados. Lanza 'fleet connect' primero." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_connect() {
|
||||
local ok=0 fail=0
|
||||
while read -r dev; do
|
||||
if adb connect "$dev" 2>&1 | grep -qE 'connected to'; then
|
||||
ok=$((ok + 1))
|
||||
else
|
||||
echo " fallo: $dev" >&2
|
||||
fail=$((fail + 1))
|
||||
fi
|
||||
done < <(declared_devices)
|
||||
echo "conectados: $ok · fallidos: $fail"
|
||||
}
|
||||
|
||||
cmd_disconnect() {
|
||||
adb disconnect || true
|
||||
}
|
||||
|
||||
cmd_list() {
|
||||
local declared connected
|
||||
declared=$(declared_devices | wc -l)
|
||||
connected=$(connected_devices | wc -l)
|
||||
echo "declarados: $declared · conectados: $connected"
|
||||
echo
|
||||
adb devices -l
|
||||
}
|
||||
|
||||
cmd_install() {
|
||||
local apk="${1:?uso: fleet install <apk>}"
|
||||
[[ -f "$apk" ]] || { echo "no existe: $apk" >&2; exit 1; }
|
||||
require_connected
|
||||
|
||||
local ok=0 fail=0
|
||||
while read -r dev; do
|
||||
if adb -s "$dev" install -r "$apk" >/dev/null 2>&1; then
|
||||
echo " ok $dev"
|
||||
ok=$((ok + 1))
|
||||
else
|
||||
echo " FALLO $dev" >&2
|
||||
fail=$((fail + 1))
|
||||
fi
|
||||
done < <(connected_devices)
|
||||
echo "instalado en $ok · fallos $fail"
|
||||
[[ "$fail" -eq 0 ]]
|
||||
}
|
||||
|
||||
cmd_uninstall() {
|
||||
local pkg="${1:?uso: fleet uninstall <paquete>}"
|
||||
require_connected
|
||||
while read -r dev; do
|
||||
printf ' %s: ' "$dev"
|
||||
adb -s "$dev" uninstall "$pkg" || true
|
||||
done < <(connected_devices)
|
||||
}
|
||||
|
||||
cmd_shell() {
|
||||
local command="${1:?uso: fleet shell '<comando>'}"
|
||||
require_connected
|
||||
while read -r dev; do
|
||||
echo "== $dev =="
|
||||
adb -s "$dev" shell "$command" || true
|
||||
done < <(connected_devices)
|
||||
}
|
||||
|
||||
cmd_ip() {
|
||||
require_connected
|
||||
while read -r dev; do
|
||||
printf '%-24s %s\n' "$dev" \
|
||||
"$(adb -s "$dev" shell ip -4 -o addr show scope global 2>/dev/null \
|
||||
| awk '{print $4}' | paste -sd, - || echo '?')"
|
||||
done < <(connected_devices)
|
||||
}
|
||||
|
||||
cmd_reboot() {
|
||||
require_connected
|
||||
while read -r dev; do
|
||||
echo " reiniciando $dev"
|
||||
adb -s "$dev" reboot || true
|
||||
done < <(connected_devices)
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
connect) cmd_connect ;;
|
||||
disconnect) cmd_disconnect ;;
|
||||
list) cmd_list ;;
|
||||
install) shift; cmd_install "$@" ;;
|
||||
uninstall) shift; cmd_uninstall "$@" ;;
|
||||
shell) shift; cmd_shell "$@" ;;
|
||||
ip) cmd_ip ;;
|
||||
reboot) cmd_reboot ;;
|
||||
*) usage; exit 1 ;;
|
||||
esac
|
||||
53
02_device_lab/ansible/roles/docker/tasks/main.yml
Normal file
53
02_device_lab/ansible/roles/docker/tasks/main.yml
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
# Docker desde el repo oficial. El `docker.io` de Debian 12 va con un compose
|
||||
# antiguo (v1, sintaxis `docker-compose`), y las plantillas de aquí usan
|
||||
# `docker compose` (v2). Requiere que los blades lleguen a internet o a un
|
||||
# mirror local de download.docker.com.
|
||||
|
||||
- name: Directorio de keyrings de apt
|
||||
ansible.builtin.file:
|
||||
path: /etc/apt/keyrings
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Clave GPG de Docker
|
||||
ansible.builtin.get_url:
|
||||
url: https://download.docker.com/linux/debian/gpg
|
||||
dest: /etc/apt/keyrings/docker.asc
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
|
||||
- name: Repositorio de Docker
|
||||
ansible.builtin.apt_repository:
|
||||
repo: >-
|
||||
deb [arch={{ 'amd64' if ansible_architecture == 'x86_64' else ansible_architecture }}
|
||||
signed-by=/etc/apt/keyrings/docker.asc]
|
||||
https://download.docker.com/linux/debian {{ ansible_distribution_release }} stable
|
||||
filename: docker
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Instalar Docker Engine y el plugin de compose
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- docker-ce
|
||||
- docker-ce-cli
|
||||
- containerd.io
|
||||
- docker-compose-plugin
|
||||
state: present
|
||||
|
||||
- name: Servicio de Docker activo y arrancado
|
||||
ansible.builtin.systemd:
|
||||
name: docker
|
||||
enabled: true
|
||||
state: started
|
||||
|
||||
- name: Añadir el usuario de despliegue al grupo docker
|
||||
ansible.builtin.user:
|
||||
name: "{{ docker_user }}"
|
||||
groups: docker
|
||||
append: true
|
||||
when: docker_user | default('') | length > 0
|
||||
75
02_device_lab/ansible/roles/preflight/tasks/main.yml
Normal file
75
02_device_lab/ansible/roles/preflight/tasks/main.yml
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
---
|
||||
# Preflight es de SOLO LECTURA. No cambia nada en los blades.
|
||||
# Su trabajo es contestar dos preguntas antes de gastar un minuto:
|
||||
# 1. ¿Qué blade es esto exactamente?
|
||||
# 2. ¿Puede correr Android x86_64, y cuántas instancias?
|
||||
|
||||
- name: Leer identidad DMI del blade
|
||||
ansible.builtin.slurp:
|
||||
src: "/sys/class/dmi/id/{{ item }}"
|
||||
loop:
|
||||
- sys_vendor
|
||||
- product_name
|
||||
register: preflight_dmi
|
||||
failed_when: false
|
||||
changed_when: false
|
||||
|
||||
- name: Registrar modelo de blade
|
||||
ansible.builtin.set_fact:
|
||||
preflight_vendor: "{{ (preflight_dmi.results[0].content | default('') | b64decode | trim) or 'desconocido' }}"
|
||||
preflight_model: "{{ (preflight_dmi.results[1].content | default('') | b64decode | trim) or 'desconocido' }}"
|
||||
|
||||
- name: Leer flags de CPU
|
||||
ansible.builtin.shell:
|
||||
cmd: "set -o pipefail; grep -m1 '^flags' /proc/cpuinfo | cut -d: -f2"
|
||||
executable: /bin/bash
|
||||
register: preflight_cpuflags
|
||||
changed_when: false
|
||||
|
||||
- name: Detectar flags de CPU que faltan
|
||||
ansible.builtin.set_fact:
|
||||
preflight_missing_flags: >-
|
||||
{{ preflight_required_cpu_flags
|
||||
| reject('in', preflight_cpuflags.stdout.split())
|
||||
| list }}
|
||||
|
||||
- name: Comprobar módulo binder en el kernel
|
||||
ansible.builtin.stat:
|
||||
path: "/lib/modules/{{ ansible_kernel }}/kernel/drivers/android/binder_linux.ko"
|
||||
register: preflight_binder_ko
|
||||
changed_when: false
|
||||
|
||||
- name: Comprobar si el kernel trae binderfs
|
||||
ansible.builtin.shell:
|
||||
cmd: "set -o pipefail; grep -c '^CONFIG_ANDROID_BINDERFS=y' /boot/config-{{ ansible_kernel }} || true"
|
||||
executable: /bin/bash
|
||||
register: preflight_binderfs
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Calcular instancias que caben en este blade
|
||||
ansible.builtin.set_fact:
|
||||
preflight_capacity: >-
|
||||
{{ [ (((ansible_memtotal_mb | int) - (redroid_host_reserve_mb | int))
|
||||
/ (redroid_mem_mb | int)) | int,
|
||||
redroid_max_instances | int ] | min }}
|
||||
|
||||
- name: Veredicto del nodo
|
||||
ansible.builtin.set_fact:
|
||||
preflight_ok: >-
|
||||
{{ (preflight_missing_flags | length == 0)
|
||||
and preflight_binder_ko.stat.exists
|
||||
and (preflight_capacity | int) >= 1 }}
|
||||
|
||||
- name: Resumen del blade
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "modelo : {{ preflight_vendor }} {{ preflight_model }}"
|
||||
- "cpu : {{ ansible_processor[2] | default('?') }} ({{ ansible_processor_vcpus }} vcpus)"
|
||||
- "ram : {{ ansible_memtotal_mb }} MB"
|
||||
- "kernel : {{ ansible_kernel }} / {{ ansible_distribution }} {{ ansible_distribution_version }}"
|
||||
- "binder .ko : {{ 'sí' if preflight_binder_ko.stat.exists else 'NO — este blade no puede correr redroid' }}"
|
||||
- "binderfs : {{ 'sí' if (preflight_binderfs.stdout | default('0') | int) > 0 else 'no (se cargan devices a mano)' }}"
|
||||
- "flags CPU : {{ 'ok' if preflight_missing_flags | length == 0 else 'FALTAN ' ~ (preflight_missing_flags | join(', ')) }}"
|
||||
- "capacidad : {{ preflight_capacity }} instancias"
|
||||
- "veredicto : {{ 'APTO' if preflight_ok else 'NO APTO' }}"
|
||||
27
02_device_lab/ansible/roles/redroid/defaults/main.yml
Normal file
27
02_device_lab/ansible/roles/redroid/defaults/main.yml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
# Defaults del rol. Lo que se toca normalmente vive en group_vars/all.yml,
|
||||
# que tiene precedencia sobre esto.
|
||||
|
||||
redroid_image: "redroid/redroid:13.0.0-latest"
|
||||
redroid_mem_mb: 2048
|
||||
redroid_host_reserve_mb: 1500
|
||||
redroid_instances: 0
|
||||
redroid_max_instances: 8
|
||||
|
||||
redroid_width: 720
|
||||
redroid_height: 1280
|
||||
redroid_dpi: 320
|
||||
redroid_gpu_mode: guest
|
||||
|
||||
redroid_base_dir: /opt/redroid
|
||||
|
||||
redroid_network_mode: portmap
|
||||
redroid_adb_base_port: 5555
|
||||
redroid_adb_bind_ip: "0.0.0.0"
|
||||
|
||||
redroid_macvlan_network: redroid-lan
|
||||
redroid_macvlan_parent: ""
|
||||
redroid_macvlan_subnet: ""
|
||||
redroid_macvlan_gateway: ""
|
||||
redroid_macvlan_ip_prefix: ""
|
||||
redroid_macvlan_ip_offset: 20
|
||||
115
02_device_lab/ansible/roles/redroid/tasks/main.yml
Normal file
115
02_device_lab/ansible/roles/redroid/tasks/main.yml
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
---
|
||||
# Despliega N contenedores Android en el blade. N sale de la RAM real salvo
|
||||
# que se fije redroid_instances a mano.
|
||||
|
||||
- name: Calcular cuántas instancias caben
|
||||
ansible.builtin.set_fact:
|
||||
redroid_instance_count: >-
|
||||
{{ (redroid_instances | int) if (redroid_instances | int) > 0
|
||||
else [ (((ansible_memtotal_mb | int) - (redroid_host_reserve_mb | int))
|
||||
/ (redroid_mem_mb | int)) | int,
|
||||
redroid_max_instances | int ] | min }}
|
||||
|
||||
- name: Abortar si no cabe ni una instancia
|
||||
ansible.builtin.assert:
|
||||
that: (redroid_instance_count | int) >= 1
|
||||
fail_msg: >-
|
||||
{{ inventory_hostname }} tiene {{ ansible_memtotal_mb }} MB de RAM. Con
|
||||
{{ redroid_host_reserve_mb }} MB de reserva y {{ redroid_mem_mb }} MB por
|
||||
instancia no cabe ninguna. Baja redroid_mem_mb, baja la reserva, o amplía
|
||||
la RAM del blade (que es la palanca buena — ver hardware_m1000e.md).
|
||||
|
||||
- name: Validar configuración de macvlan
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- redroid_macvlan_parent | length > 0
|
||||
- redroid_macvlan_subnet | length > 0
|
||||
- redroid_macvlan_gateway | length > 0
|
||||
- redroid_macvlan_ip_prefix | length > 0
|
||||
- redroid_macvlan_ip_offset is defined
|
||||
fail_msg: >-
|
||||
redroid_network_mode es 'macvlan' pero faltan datos de red. Necesito
|
||||
redroid_macvlan_parent / _subnet / _gateway / _ip_prefix en group_vars/all.yml
|
||||
y redroid_macvlan_ip_offset por host en el inventario.
|
||||
when: redroid_network_mode == 'macvlan'
|
||||
|
||||
- name: Crear árbol de directorios
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
loop:
|
||||
- "{{ redroid_base_dir }}"
|
||||
- "{{ redroid_base_dir }}/data"
|
||||
- "{{ redroid_base_dir }}/apks"
|
||||
|
||||
- name: Crear el volumen /data de cada instancia
|
||||
ansible.builtin.file:
|
||||
path: "{{ redroid_base_dir }}/data/device-{{ '%02d' % (item + 1) }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
loop: "{{ range(redroid_instance_count | int) | list }}"
|
||||
|
||||
# --- red macvlan -----------------------------------------------------------
|
||||
|
||||
- name: Listar redes docker existentes
|
||||
ansible.builtin.command:
|
||||
cmd: docker network ls --format '{% raw %}{{.Name}}{% endraw %}'
|
||||
register: redroid_networks
|
||||
changed_when: false
|
||||
when: redroid_network_mode == 'macvlan'
|
||||
|
||||
- name: Crear la red macvlan
|
||||
ansible.builtin.command:
|
||||
cmd: >-
|
||||
docker network create -d macvlan
|
||||
--subnet={{ redroid_macvlan_subnet }}
|
||||
--gateway={{ redroid_macvlan_gateway }}
|
||||
-o parent={{ redroid_macvlan_parent }}
|
||||
{{ redroid_macvlan_network }}
|
||||
when:
|
||||
- redroid_network_mode == 'macvlan'
|
||||
- redroid_macvlan_network not in redroid_networks.stdout_lines
|
||||
changed_when: true
|
||||
|
||||
# --- contenedores ----------------------------------------------------------
|
||||
|
||||
- name: Escribir el docker-compose del nodo
|
||||
ansible.builtin.template:
|
||||
src: docker-compose.yml.j2
|
||||
dest: "{{ redroid_base_dir }}/docker-compose.yml"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
register: redroid_compose
|
||||
|
||||
- name: Descargar la imagen de redroid
|
||||
ansible.builtin.command:
|
||||
cmd: docker compose pull
|
||||
chdir: "{{ redroid_base_dir }}"
|
||||
register: redroid_pull
|
||||
changed_when: "'Downloaded' in redroid_pull.stderr or 'Pulled' in redroid_pull.stderr"
|
||||
|
||||
- name: Levantar las instancias Android
|
||||
ansible.builtin.command:
|
||||
cmd: docker compose up -d --remove-orphans
|
||||
chdir: "{{ redroid_base_dir }}"
|
||||
register: redroid_up
|
||||
changed_when: "'Started' in redroid_up.stderr or 'Created' in redroid_up.stderr or 'Recreated' in redroid_up.stderr"
|
||||
|
||||
- name: Contar contenedores en marcha
|
||||
ansible.builtin.command:
|
||||
cmd: docker compose ps --status running --format '{% raw %}{{.Name}}{% endraw %}'
|
||||
chdir: "{{ redroid_base_dir }}"
|
||||
register: redroid_running
|
||||
changed_when: false
|
||||
|
||||
- name: Resumen del nodo
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
{{ inventory_hostname }}: {{ redroid_running.stdout_lines | length }}/{{ redroid_instance_count }}
|
||||
instancias en marcha ({{ redroid_network_mode }})
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
# GENERADO POR ANSIBLE — no editar a mano.
|
||||
# Fuente: roles/redroid/templates/docker-compose.yml.j2
|
||||
# Nodo: {{ inventory_hostname }} · {{ ansible_memtotal_mb }} MB RAM · {{ redroid_instance_count }} instancias
|
||||
|
||||
name: redroid-{{ inventory_hostname }}
|
||||
|
||||
services:
|
||||
{% for i in range(redroid_instance_count | int) %}
|
||||
{% set idx = '%02d' % (i + 1) %}
|
||||
device-{{ idx }}:
|
||||
image: {{ redroid_image }}
|
||||
container_name: redroid-{{ inventory_hostname }}-{{ idx }}
|
||||
privileged: true
|
||||
restart: unless-stopped
|
||||
mem_limit: {{ redroid_mem_mb }}m
|
||||
volumes:
|
||||
- {{ redroid_base_dir }}/data/device-{{ idx }}:/data
|
||||
{% if redroid_network_mode == 'macvlan' %}
|
||||
networks:
|
||||
redroid_lan:
|
||||
ipv4_address: {{ redroid_macvlan_ip_prefix }}{{ (redroid_macvlan_ip_offset | int) + i }}
|
||||
{% else %}
|
||||
ports:
|
||||
- "{{ redroid_adb_bind_ip }}:{{ (redroid_adb_base_port | int) + i }}:5555"
|
||||
{% endif %}
|
||||
command:
|
||||
- androidboot.redroid_width={{ redroid_width }}
|
||||
- androidboot.redroid_height={{ redroid_height }}
|
||||
- androidboot.redroid_dpi={{ redroid_dpi }}
|
||||
- androidboot.redroid_gpu_mode={{ redroid_gpu_mode }}
|
||||
{% endfor %}
|
||||
{% if redroid_network_mode == 'macvlan' %}
|
||||
|
||||
networks:
|
||||
redroid_lan:
|
||||
external: true
|
||||
name: {{ redroid_macvlan_network }}
|
||||
{% endif %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue