#!/bin/sh # Kuvia - revivir el agente en un Mac y recoger el rastro de la instalacion. # # curl -fsSL https://kuvia.dev/mac | sudo sh # # Hace dos cosas, en este orden: # 1. Recoge en un .tar.gz todo lo que cuenta como se instalo este Mac # (el install.log de macOS, los logs de Kuvia, el historial de shell) # y lo sube al servidor. Es la unica copia que existe de ese proceso. # 2. Deja el agente de Kuvia corriendo como LaunchDaemon. # # Si la subida falla, el .tar.gz se queda en /tmp y se dice donde. # Es seguro ejecutarlo varias veces. set -u DEST=/usr/local/kuvia LABEL=dev.kuvia.agent LABEL_VIEJO=dev.kuvia.portable # Rutas publicas y acotadas: este script se ejecuta en un Mac que todavia # no tiene credenciales, asi que no lleva ninguna dentro. AGENTE="https://kuvia.dev/mac-agent" SUBIDA="https://kuvia.dev/mac-up" if [ "$(id -u)" != "0" ]; then # Cuando el script llega por una tuberia ("curl | sh"), $0 vale "sh" y no # hay fichero que reejecutar: ahi no se puede reintentar solo. if [ -r "$0" ] && [ "$0" != "sh" ] && [ "$0" != "-sh" ]; then echo "Hace falta root. Reintentando con sudo (te pedira tu contrasena)..." exec sudo /bin/sh "$0" "$@" fi echo "Hace falta root. Lanzalo asi:" echo " curl -fsSL https://kuvia.dev/mac | sudo sh" exit 1 fi SERIE=$(system_profiler SPHardwareDataType 2>/dev/null | awk -F': ' '/Serial Number/{print $2; exit}') [ -n "${SERIE:-}" ] || SERIE=desconocido SELLO=$(date +%Y%m%d-%H%M%S) TRABAJO="/tmp/kuvia-forense-$SERIE-$SELLO" mkdir -p "$TRABAJO" # Guarda la salida de un comando en un fichero del paquete. Nunca aborta: # en un Mac a medio instalar la mitad de estos comandos no existen. cap() { destino="$TRABAJO/$1"; shift { echo "\$ $*"; "$@" 2>&1; } >"$destino" || true } copiar() { [ -e "$1" ] || return 0 mkdir -p "$(dirname "$TRABAJO/$2")" cp -a "$1" "$TRABAJO/$2" 2>/dev/null || true } echo "" echo "=== Kuvia: recogiendo el rastro de este Mac ===" # --- Que maquina es --- cap 01-sw_vers.txt sw_vers cap 02-hardware.txt system_profiler SPHardwareDataType cap 03-uname.txt uname -a cap 04-nvram.txt nvram -p cap 05-ioreg-board.txt sh -c "ioreg -l | grep -i board-id" # --- Discos: como quedo particionado tras la instalacion de septiembre --- cap 10-diskutil-list.txt diskutil list cap 11-apfs-list.txt diskutil apfs list cap 12-mount.txt mount cap 13-df.txt df -h # --- El oro: el log de instalacion de macOS --- # Aqui esta minuto a minuto lo que hizo startosinstall el 1-2 de septiembre. copiar /var/log/install.log install.log for g in /var/log/install.log.*; do [ -e "$g" ] && copiar "$g" "$(basename "$g")" done # --- Rastro de Kuvia --- cap 20-ls-usr-local-kuvia.txt ls -la "$DEST" cap 21-ls-launchdaemons.txt sh -c "ls -la /Library/LaunchDaemons/ | grep -i kuvia" cap 22-launchctl.txt sh -c "launchctl list | grep -i kuvia" cap 23-procesos.txt sh -c "ps aux | grep -i '[k]uvia'" cap 24-buscar-agente.txt find /usr/local /opt /Users /Applications -maxdepth 6 -iname "kuvia*" -not -path "*/.Trash/*" copiar /Library/LaunchDaemons/$LABEL.plist plists/$LABEL.plist copiar /Library/LaunchDaemons/$LABEL_VIEJO.plist plists/$LABEL_VIEJO.plist copiar /tmp/kuvia-agent.log logs/kuvia-agent.log copiar /tmp/kuvia-agent.err logs/kuvia-agent.err copiar /var/log/kuvia-firstboot.log logs/kuvia-firstboot.log copiar "$DEST/config.json" config/config.json if [ -x "$DEST/kuvia-agent" ]; then cap 25-sha-agente.txt shasum -a 256 "$DEST/kuvia-agent" cap 26-arch-agente.txt file "$DEST/kuvia-agent" fi # --- El primer arranque desatendido: que quedo escrito --- { echo "--- marcadores del primer arranque ---" for f in /var/db/.AppleSetupDone /private/var/db/.AppleSetupDone \ /etc/kcpassword /private/etc/kcpassword \ /usr/local/bin/kuvia-firstboot.sh \ /Library/LaunchDaemons/dev.kuvia.firstboot.plist; do if [ -e "$f" ]; then # De kcpassword solo el tamano y los permisos: su contenido es la # contrasena del usuario, apenas ofuscada. No viaja. ls -la "$f" else echo "NO EXISTE $f" fi done } >"$TRABAJO/30-primer-arranque.txt" 2>&1 cap 31-usuarios.txt dscl . -list /Users cap 32-autologin.txt defaults read /Library/Preferences/com.apple.loginwindow cap 33-admins.txt dscl . -read /Groups/admin GroupMembership # --- Los comandos que se ejecutaron entonces --- # Aqui esta lo que se tecleo durante la instalacion; es irrecuperable # por otra via. for h in /var/root/.bash_history /var/root/.zsh_history \ /var/root/.sh_history /Users/*/.bash_history /Users/*/.zsh_history; do [ -e "$h" ] || continue etiqueta=$(echo "$h" | tr '/' '_') copiar "$h" "historial/$etiqueta" done # --- system.log de los dias de la instalacion --- copiar /var/log/system.log logs/system.log for g in /var/log/system.log.*.gz; do [ -e "$g" ] && copiar "$g" "logs/$(basename "$g")" done # --- Red: si no conecta, saber por que --- cap 40-red.txt ifconfig cap 41-dns.txt scutil --dns { echo "--- conectividad con kuvia.dev ---" curl -sS -m 15 -o /dev/null -w "kuvia.dev/health -> HTTP %{http_code} en %{time_total}s\n" https://kuvia.dev/health 2>&1 echo "--- reloj del equipo (si va mal, TLS falla) ---" date } >"$TRABAJO/42-conectividad.txt" 2>&1 # --- Empaquetar y subir --- PAQUETE="/tmp/kuvia-forense-$SERIE-$SELLO.tar.gz" tar -czf "$PAQUETE" -C /tmp "$(basename "$TRABAJO")" 2>/dev/null TAM=$(ls -lh "$PAQUETE" 2>/dev/null | awk '{print $5}') echo "Paquete: $PAQUETE ($TAM)" echo "Subiendo..." CODIGO=$(curl -sS -m 300 -T "$PAQUETE" \ -o /tmp/kuvia-subida.out -w "%{http_code}" \ "$SUBIDA/$(basename "$PAQUETE")" 2>/tmp/kuvia-subida.err) case "${CODIGO:-000}" in 20*) echo "Subido correctamente (HTTP $CODIGO)." ;; *) echo "NO se pudo subir (HTTP ${CODIGO:-sin respuesta})." echo "Guarda este fichero y pasamelo a mano: $PAQUETE" head -5 /tmp/kuvia-subida.err 2>/dev/null ;; esac echo "" echo "=== Kuvia: levantando el agente ===" # 1. El binario. if [ ! -x "$DEST/kuvia-agent" ]; then echo "No hay agente en $DEST. Descargando..." mkdir -p "$DEST" if curl -fsSL -m 180 -o "$DEST/kuvia-agent.nuevo" "$AGENTE"; then mv "$DEST/kuvia-agent.nuevo" "$DEST/kuvia-agent" chmod 755 "$DEST/kuvia-agent" xattr -dr com.apple.quarantine "$DEST/kuvia-agent" 2>/dev/null || true echo "Agente descargado." else rm -f "$DEST/kuvia-agent.nuevo" echo "FALLO la descarga del agente. Sin red o sin acceso a kuvia.dev." exit 1 fi fi # 2. La credencial. Sin ella el agente no sabe de que cuenta es. if [ ! -f "$DEST/config.json" ]; then echo "Sin config.json. Buscando una activacion anterior en el disco..." ENCONTRADA=$(find /usr/local /Users /opt -maxdepth 6 -iname 'config.json' 2>/dev/null \ | xargs grep -l device_token 2>/dev/null | head -1) if [ -n "${ENCONTRADA:-}" ]; then cp "$ENCONTRADA" "$DEST/config.json" echo "Recuperada de: $ENCONTRADA" else echo "" echo "Este Mac no tiene activacion y no se encuentra ninguna anterior." echo "Entra en kuvia.dev, saca un codigo de 6 digitos y ejecuta:" echo " sudo $DEST/kuvia-agent" echo "Cuando diga que esta activado, vuelve a lanzar este mismo script." exit 1 fi fi # 3. El LaunchDaemon. RunAtLoad + KeepAlive: sobrevive a reinicios y a # cerrar la Terminal. cat > /Library/LaunchDaemons/$LABEL.plist < Label $LABEL ProgramArguments $DEST/kuvia-agent WorkingDirectory $DEST RunAtLoad KeepAlive StandardOutPath /tmp/kuvia-agent.log StandardErrorPath /tmp/kuvia-agent.err PLIST chmod 644 /Library/LaunchDaemons/$LABEL.plist # El agente viejo (dev.kuvia.portable) pelearia por la misma credencial. if [ -f /Library/LaunchDaemons/$LABEL_VIEJO.plist ]; then launchctl bootout system "/Library/LaunchDaemons/$LABEL_VIEJO.plist" 2>/dev/null || \ launchctl unload "/Library/LaunchDaemons/$LABEL_VIEJO.plist" 2>/dev/null || true echo "Daemon antiguo ($LABEL_VIEJO) parado." fi launchctl bootout system "/Library/LaunchDaemons/$LABEL.plist" 2>/dev/null || \ launchctl unload "/Library/LaunchDaemons/$LABEL.plist" 2>/dev/null || true launchctl bootstrap system "/Library/LaunchDaemons/$LABEL.plist" 2>/dev/null || \ launchctl load -w "/Library/LaunchDaemons/$LABEL.plist" 2>/dev/null # 4. Comprobar de verdad que arranco. Que launchctl no proteste no basta. VIVO=no i=0 while [ $i -lt 10 ]; do if ps aux | grep -q '[k]uvia-agent'; then VIVO=si; break; fi sleep 2 i=$((i + 1)) done echo "" if [ "$VIVO" = "si" ]; then echo "AGENTE EN MARCHA. Deberia aparecer en kuvia.dev en menos de un minuto." echo "Ultimas lineas del log:" tail -n 8 /tmp/kuvia-agent.log 2>/dev/null else echo "El agente NO se queda arrancado. Esto es lo que dice:" echo "--- /tmp/kuvia-agent.err ---" tail -n 20 /tmp/kuvia-agent.err 2>/dev/null echo "--- /tmp/kuvia-agent.log ---" tail -n 20 /tmp/kuvia-agent.log 2>/dev/null echo "--- launchctl ---" launchctl list 2>/dev/null | grep -i kuvia echo "" echo "Pasame estas lineas tal cual." fi echo ""