#!/bin/sh
# Il soccorso di Velmo (spec 2026-09-21 §10). Sta nella parte di sola lettura
# dell'immagine, e parte all'avvio prima di velmod: un aggiornamento rotto non
# puo' rompere chi deve ripararlo.
#
# Guarda una cosa sola: c'era un aggiornamento in corso (il marcatore
# "tentativo" e' ancora aperto) e velmod non l'ha chiuso da se'? Se si', conta.
# Oltre la soglia toglie Velmo dall'overlay e riavvia: torna quello di
# fabbrica, che sta qui nella rom e non si e' mai mosso.
#
# Su un'immagine Velmo /overlay copre /rom, e togliere le voci dall'overlay fa
# riaffiorare la rom: si torna alla versione di fabbrica, senza spazio in piu'.
# ha niente di Velmo: li' si rimette la versione di prima, che
# applica-aggiornamento.sh tiene in $PRECEDENTE con un collegamento (22
# settembre 2026 - prima, il ritorno avrebbe cancellato Velmo).
# comune.sh sorgente gia' router.conf: sorgerla anche qui, come ogni altro
# comando privilegiato, invece di sorgere solo router.conf, e' cio' che rende
# possibile chiamare prepara_aggiornamento sotto - lo stesso posto solo che
# la prepara per ogni modo in cui puo' nascere.
. /usr/lib/velmo/comune.sh

STATO=/etc/velmo/aggiornamento
SUPERIORE=${VELMO_OVERLAY_SUPERIORE:-/overlay/upper}
SOGLIA_RITORNO=${VELMO_SOCCORSO_SOGLIA:-3}
SOGLIA_CHIAVETTA=${VELMO_SOCCORSO_SOGLIA_USB:-5}
# La versione di prima dei file sovrascritti, tenuta da applica-aggiornamento.sh
# per i file che nella rom non ci sono (un Velmo installato con installa.sh).
PRECEDENTE=${VELMO_PRECEDENTE:-/etc/velmo/precedente}
ROM=${VELMO_ROM:-/rom}

# rimetti <percorso>: la versione di prima se e' stata tenuta, altrimenti si
# toglie la voce dallo strato superiore e riaffiora la rom (o, per un file che
# il pacchetto ha aggiunto, sparisce).
#
# Il percorso deve cadere **dentro** $PRECEDENTE o $SUPERIORE dopo risoluzione
# (readlink -f). Un elenco di file scritti alterato non deve poter dire
# «togli /etc/passwd» o «togli /../../../root/.ssh/authorized_keys»: qui si
# tocca solo lo strato superiore dell'overlay o la copia di riserva, mai
# qualunque cosa il path risolto nomini (audit 24 settembre 2026, C2).
rimetti() {
	case "$1" in
	/*) ;;
	*) logger -p daemon.err -t velmo-soccorso "rimetti: percorso non assoluto $1"; return 1 ;;
	esac
	risolto=$(readlink -f "$SUPERIORE$1" 2>/dev/null) || risolto=
	salvo=$(readlink -f "$PRECEDENTE$1" 2>/dev/null) || salvo=
	superiore_ok=$(readlink -f "$SUPERIORE" 2>/dev/null) || return 1
	precedente_ok=$(readlink -f "$PRECEDENTE" 2>/dev/null) || precedente_ok=
	case "$risolto" in
	"$superiore_ok"/*|"$superiore_ok") ;;
	*)
		logger -p daemon.err -t velmo-soccorso "rimetti: $1 risolve fuori da $SUPERIORE ($risolto): salto"
		return 1
		;;
	esac
	if [ -f "$PRECEDENTE$1" ]; then
		case "$salvo" in
		"$precedente_ok"/*|"$precedente_ok") ;;
		*)
			logger -p daemon.err -t velmo-soccorso "rimetti: la copia di $1 risolve fuori da $PRECEDENTE ($salvo): salto"
			return 1
			;;
		esac
		mkdir -p "$(dirname "$risolto")"
		mv -f "$salvo" "$risolto"
	else
		rm -f "$risolto"
	fi
}

# Il soccorso gira a **ogni avvio**, prima di velmod, ed e' spesso lui il
# primo a creare questa cartella: su un Velmo appena uscito di fabbrica,
# senza il chown di prepara_aggiornamento (comune.sh) root la creerebbe qui e
# velmod - che gira senza privilegi - non riuscirebbe mai a scriverci
# ultimo-controllo, visto, manifesto (trovato in revisione, 21
# settembre 2026): la scoperta degli aggiornamenti non funzionerebbe affatto,
# e comincerebbe a funzionare solo dopo il primo aggiornamento riuscito,
# cioe' mai, perche' il primo passa proprio da qui.
prepara_aggiornamento "$STATO"

# Nessun aggiornamento in corso: niente da fare, e il contatore si azzera.
# L'ultimo aggiornamento, se c'e' stato, si e' retto: i file della versione di
# prima non servono piu'.
#
# **STATO_PRIMA e SERVIZI_PRIMA non si cancellano qui**, e fino al 23 settembre
# 2026 si cancellavano. L'idea era «un ritorno rimette com'era prima
# dell'ultimo cambiamento», e suona bene finche' non si guarda **chi** ha fatto
# l'ultimo cambiamento: cancellandoli a ogni avvio, il minuto dopo la guardia
# rimetteva da parte lo stato di adesso - che e' gia' quello scritto da Velmo,
# SSH spento e Interface dichiarata - e lo chiamava «prima». Dal secondo
# aggiornamento in poi il ritorno di fabbrica restituiva un apparecchio con SSH
# spento, cioe' C1 del Task 5 riaperto, sul solo apparecchio di prova che esiste
# (trovato in revisione, 23 settembre 2026, misurato con due giri).
#
# La linea di base e' una sola e non si muove: com'era **prima che Velmo
# toccasse quella sezione**. E' l'unico stato di cui si sappia che
# l'apparecchio si raggiunge - dropbear come l'ha lasciato il firmware - ed e'
# quello che serve a chi deve rientrare. La cancella solo
# rimetti_stato_di_prima, dopo averla rimessa: da li' la passata dopo la rifa'
# dallo stato appena ripristinato, che e' di nuovo quello di fabbrica.
[ -f "$STATO/tentativo" ] || { rm -f "$STATO/avvii"; rm -rf "$PRECEDENTE"; exit 0; }

# Si conta ogni avvio in cui il marcatore "tentativo" e' ancora aperto:
# velmod lo chiude da se' - senza scrivere nient'altro - quando si e' retto un
# minuto (control/internal/web, SegnaVita). Non c'e' un segno di vita separato
# da guardare: era un file a parte in /var/run, ma nessuno lo rileggeva mai -
# ne' questo script ne' altro - ed e' stato tolto (revisione, 21 settembre
# 2026).
avvii=$(cat "$STATO/avvii" 2>/dev/null || echo 0)
avvii=$((avvii + 1))
printf '%s\n' "$avvii" > "$STATO/avvii"
sync
logger -t velmo-soccorso "aggiornamento in corso non ancora riuscito: avvio $avvii"

if [ "$avvii" -gt "$SOGLIA_CHIAVETTA" ]; then
	# Nemmeno il Velmo di fabbrica si alza: si cerca un pacchetto sulla
	# chiavetta. E' l'unico posto dove si puo' anche scendere di versione, ed e'
	# legato allo stato di guasto proprio per questo: altrove, infilare una
	# chiavetta e riavviare sarebbe il modo piu' facile di riportare un Velmo a
	# una versione con un difetto noto.
	#
	# ATTENZIONE (corretto in revisione, 21 settembre 2026): con le soglie di
	# default questo ramo non e' raggiungibile IN NESSUN CASO, non solo "nel
	# funzionamento normale" - nemmeno in quello per cui e' scritto, un Velmo
	# di fabbrica che a sua volta non si alza piu'. Il motivo non e' che il
	# ritorno di fabbrica "di solito" scatta prima: scatta SEMPRE prima, per
	# costruzione. avvii cresce di uno a ogni avvio, e i due controlli
	# guardano lo stesso valore nella stessa esecuzione: siccome
	# SOGLIA_CHIAVETTA (5) e' maggiore di SOGLIA_RITORNO (3), avvii tocca
	# SOGLIA_RITORNO+1 prima di poter mai toccare SOGLIA_CHIAVETTA+1, e a quel
	# punto il ramo sotto cancella "tentativo" e "avvii" e riavvia - senza
	# guardare se il Velmo di fabbrica a cui si torna si regge davvero. Se non
	# si regge, il prossimo avvio trova "tentativo" gia' cancellato ed esce
	# subito (la riga poco sopra), quindi il contatore non riparte mai: la
	# chiavetta resta un ripiego scritto (soccorso-chiavetta.sh esiste) ma
	# irraggiungibile da qui, con queste soglie, in qualunque sequenza di
	# guasti. Per renderlo raggiungibile andrebbe invertito l'ordine delle
	# soglie o tolto l'azzeramento del contatore al ritorno di fabbrica - non
	# e' stato fatto qui: e' una decisione che spetta al committente, non a
	# questa correzione.
	# La stessa cartella di sempre (VELMO_DATI, router.conf: /mnt/velmo-data),
	# non una scansione di /mnt/*: erano due idee diverse di "chiavetta
	# inserita" - l'hotplug dei registri (05-velmo-dati) guarda solo li'
	# (trovato in revisione, 21 settembre 2026).
	d=${VELMO_DATI:-/mnt/velmo-data}
	if [ -d "$d" ]; then
		# Gli stessi nomi che accetta l'hotplug (05-velmo-dati): anche quelli
		# con cui velmo.one pubblica i file, non solo velmo-*.pacchetto.
		for p in "$d"/velmo-*.pacchetto "$d"/liste-*.tar.gz "$d"/software-*.tar.gz; do
			[ -f "$p" ] || continue
			logger -t velmo-soccorso "provo il pacchetto $p dalla chiavetta"
			/usr/lib/velmo/soccorso-chiavetta.sh "$p" && {
				printf 'esito chiavetta\nquando %s\npacchetto %s\n' \
					"$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$p" > "$STATO/ritorno"
				sync; reboot -f
			}
		done
	fi
	exit 0
fi

if [ "$avvii" -gt "$SOGLIA_RITORNO" ]; then
	logger -p daemon.err -t velmo-soccorso "torno al Velmo di fabbrica: $avvii avvii con l'aggiornamento ancora in corso"
	# Si tolgono le voci dallo **strato superiore**, non dalla vista unita:
	# cancellare dalla vista unita crea un marcatore di cancellazione e fa
	# sparire il file, invece di far riaffiorare quello della rom.
	# VELMO_OVERLAY_SUPERIORE e' confermato sul banco (router.conf, 21 settembre
	# 2026): /proc/mounts dice upperdir=/overlay/upper.
	# "file-scritti" si accetta solo con la sentinella finale "fine": la
	# rinomina atomica con cui applica-aggiornamento.sh lo scrive dovrebbe gia'
	# bastare a escludere un file a meta', ma la sentinella copre anche il
	# caso in cui qualcosa si comporti diversamente da come ce lo aspettiamo,
	# e costa una riga (revisione del 21 settembre 2026: un file
	# troncato - la corrente mancata proprio durante quella scrittura, con un
	# solo sync a fine funzione - passava il vecchio controllo "[ -s ]", che
	# guarda solo "non vuoto", e faceva disfare al soccorso una parte sola di
	# cio' che era stato scritto, dichiarando comunque "esito fabbrica").
	# L'elenco dei file scritti vive in $STATO_ROOT (root 0700), non in $STATO
	# che e' di «velmo»: un elenco che root rilegge per cancellare file non
	# puo' essere riscritto da un processo senza privilegi (audit 24 settembre
	# 2026, C2). Resta accettato anche quello vecchio in $STATO, ma solo
	# perche' la riga sopra e i nomi sono gia' validati da rimetti: un
	# aggiornamento da una versione vulnerabile non deve perdere la via di
	# ritorno, ma non deve nemmeno eseguire un elenco alterato.
	if [ -s "$STATO_ROOT/file-scritti" ]; then
		elenco_scritti=$STATO_ROOT/file-scritti
	elif [ -s "$STATO/file-scritti" ]; then
		elenco_scritti=$STATO/file-scritti
	else
		elenco_scritti=
	fi
	if [ -n "$elenco_scritti" ] && [ "$(tail -n 1 "$elenco_scritti")" = fine ]; then
		# L'elenco e' completo: si toglie esattamente cio' che e' stato
		# scritto, non un elenco fisso che potrebbe disfare meno del vero
		# (se il pacchetto avesse toccato anche velmo-richieste o
		# velmo-modo, per dire, un elenco fisso non lo saprebbe).
		while read -r f; do
			[ -n "$f" ] && [ "$f" != fine ] || continue
			rimetti "$f"
		done < "$elenco_scritti"
	else
		# Elenco assente, o senza la sentinella finale - non ci si fida di
		# una lettura parziale. **Quando l'elenco di cio' che e' stato
		# scritto non e' affidabile, si fa la pulizia PIU' LARGA, non la piu'
		# stretta**: un ritorno che sbaglia deve sbagliare togliendo troppo -
		# si torna a una fabbrica funzionante - non troppo poco, che
		# lascerebbe in vigore un miscuglio di due versioni che nessuno ha
		# mai provato. Per questo l'elenco fisso qui sotto copre l'intero
		# perimetro che un pacchetto "software" puo' toccare (lo stesso
		# elenco di applica-aggiornamento.sh), non solo tre binari a memoria.
		[ -n "$elenco_scritti" ] &&
			logger -p daemon.err -t velmo-soccorso "elenco di cio' che e' stato scritto incompleto: torno con l'elenco piu' largo"
		# Prima la versione di prima, dove c'e': su un Velmo installato con
		# installa.sh e' l'unica cosa che c'e' da rimettere, perche' la rom
		# non ha niente di Velmo.
		if [ -d "$PRECEDENTE" ]; then
			( cd "$PRECEDENTE" && find . -type f ) | while read -r f; do
				rimetti "${f#.}"
			done
		fi
		# Poi il perimetro largo, ma solo cio' che la rom sa rimettere: senza
		# una copia nella rom, togliere la voce cancellerebbe Velmo.
		for f in $(cd "$SUPERIORE" 2>/dev/null && find usr etc -type f 2>/dev/null | grep -E '^(usr/sbin/velmo|usr/lib/velmo/|etc/init.d/velmo|etc/hotplug.d/[^/]*/[^/]*velmo|etc/velmo/router.conf$|etc/velmo/matrice.conf$|etc/velmo/famiglie-di-fabbrica$)'); do
			[ -e "$ROM/$f" ] && rm -f "$SUPERIORE/$f"
		done
	fi
	# E la **configurazione**, non solo i file. Rimettere i file e basta
	# restituiva un apparecchio irraggiungibile, in due modi:
	#
	#   - pulisci_deviazione_console ha cancellato e committato le sezioni fw3
	#     che portavano la console sulla porta di prima, e il velmod rimesso
	#     ascolta di nuovo li' (N1, Task 3);
	#   - lega_ssh_a_casa ha chiuso dropbear - dal 23 settembre 2026 lo fa
	#     sempre, perche' l'apparecchio e' chiuso - e il comune.sh della versione
	#     precedente, quello che si rimette qui sopra, di dropbear non sa niente:
	#     il collegamento d'amministrazione resterebbe chiuso **per sempre** (C1,
	#     Task 5). Da quando non c'e' piu' un interruttore che lo riaccenda,
	#     questa e' la sola cosa che tiene aperto il ritorno, e la sorveglia un
	#     giro doppio (controlli.py,
	#     il_ritorno_di_fabbrica_rimette_dropbear_com_era).
	#
	# Il codice che rimette e' quello nuovo, gia' in memoria quando gira:
	# rimetterlo indietro insieme al resto non lo toglie a questa esecuzione.
	if rimetti_stato_di_prima; then
		logger -t velmo-soccorso "rimessa la configurazione di prima dell'aggiornamento"
	fi
	rm -f "$STATO/tentativo" "$STATO/avvii" "$STATO/file-scritti" "$STATO_ROOT/file-scritti"
	printf 'esito fabbrica\nquando %s\ndopo %s avvii\n' \
		"$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$avvii" > "$STATO/ritorno"
	sync
	reboot -f
fi
exit 0
