<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>scripting &amp;mdash; Cyberdyne Systems</title>
    <link>https://noblogo.org/aytin/tag:scripting</link>
    <description>&#34;Fare o non fare. Non c&#39;è provare!&#34;</description>
    <pubDate>Thu, 30 Apr 2026 09:53:55 +0000</pubDate>
    <item>
      <title>Aggiornare LibreOffice su Fedora</title>
      <link>https://noblogo.org/aytin/aggiornare-libreoffice-su-fedora</link>
      <description>&lt;![CDATA[office&#xA;&#xA;Piccolo script scritto di fretta per avere sotto mano la versione fresh di Libreoffice su Fedora invece che quella pacchettizzata. Un giorno ci ritornerò come ho fatto con &#34;Gestione TOTP in bash&#34;&#xA;!--more--&#xA;&#xA;!/bin/bash&#xA;download.documentfoundation.org&#xA;&#xA;usage() {&#xA;cat&lt;&lt;EOF&#xA;Usa come: ./libreoffice-update.sh [options]&#xA;dove:&#xA;    [options]&#xA;        -u verifica l esistenza di una nuova versione&#xA;        -i numeroversion installa la nuova versione di libreoffice&#xA;        -h stampa questa pagina di help&#xA;&#xA;ESEMPI:&#xA;    VERIFICA CHE UN AGGIORNAMENTO SIA DISPONIBILE&#xA;    libreoffice-update -u&#xA;&#xA;    INSTALLA L ULTIMA VERSIONE DI LIBREOFFICE&#xA;    libreoffice-update -i&#xA;&#xA;    VISUALIZZA l HELP&#xA;    libreoffice-update -h&#xA;EOF&#xA;}&#xA;&#xA;downloadlosuite() {&#xA;    echo -n &#34;Inizio download LibreOffice $VERSION... &#34;&#xA;    [[ $(aria2c https://download.documentfoundation.org/libreoffice/stable/&#34;$VERSION&#34;/rpm/x8664/LibreOffice&#34;$VERSION&#34;Linuxx86-64rpm.tar.gz | grep ERROR) ]] \&#xA;        &amp;&amp; { echo &#34; Impossibile scaricare LibreOffice $VERSION &#34;; ERRORCODE=64; return ${ERRORCODE}; } \&#xA;        || echo &#34;Fatto.&#34;&#xA;&#xA;    echo -n &#34;Inizio download LibreOfficeLangack $VERSION... &#34;&#xA;    [[ $(aria2c https://download.documentfoundation.org/libreoffice/stable/&#34;$VERSION&#34;/rpm/x8664/LibreOffice&#34;$VERSION&#34;Linuxx86-64rpmlangpackit.tar.gz | grep ERROR) ]] \&#xA;        &amp;&amp; { echo &#34; Impossibile scaricare LibreOfficelangPack $VERSION &#34;; ERRORCODE=65; return ${ERRORCODE}; } \&#xA;        || echo &#34;Fatto.&#34;&#xA;&#xA;    echo -n &#34;Inizio download LibreOfficeHelppack $VERSION... &#34;&#xA;    [[ $(aria2c https://download.documentfoundation.org/libreoffice/stable/&#34;$VERSION&#34;/rpm/x8664/LibreOffice&#34;$VERSION&#34;Linuxx86-64rpmhelppackit.tar.gz | grep ERROR) ]] \&#xA;        &amp;&amp; { echo &#34; Impossibile scaricare LibreOfficeHelppack $VERSION &#34;; ERRORCODE=66; return ${ERRORCODE}; } \&#xA;        || echo -e &#34;Fatto.\n&#34;&#xA;}&#xA;&#xA;decomprimilosuite() {&#xA;    echo -n &#34;Decompressione tar LibreOffice $VERSION in corso... &#34;&#xA;    [[ $(tar -xzf LibreOffice&#34;$VERSION&#34;Linuxx86-64rpm.tar.gz 2  /dev/null) ]] \&#xA;        &amp;&amp; { echo &#34; Impossibile decomprimere il tar di LibreOffice $VERSION &#34;; ERRORCODE=67; return ${ERRORCODE}; } \&#xA;        || echo &#34;Fatto.&#34;&#xA;&#xA;    echo -n &#34;Decompressione tar LibreOfficeLangpack $VERSION in corso... &#34;&#xA;    [[ $(tar -xzf LibreOffice&#34;$VERSION&#34;Linuxx86-64rpmlangpackit.tar.gz  2  /dev/null) ]] \&#xA;        &amp;&amp; { echo &#34; Impossibile decomprimere il tar di LibreOfficeLangpack $VERSION &#34;; ERRORCODE=68; return ${ERRORCODE}; } \&#xA;        || echo &#34;Fatto.&#34;&#xA;&#xA;    echo -n &#34;Decompressione tar LibreOfficeHelppack $VERSION in corso... &#34;&#xA;    [[ $(tar -xzf LibreOffice&#34;$VERSION&#34;Linuxx86-64rpmhelppackit.tar.gz 2  /dev/null) ]] \&#xA;        &amp;&amp; { echo &#34; Impossibile decomprimere il tar di LibreOfficeHelppack $VERSION &#34;; ERRORCODE=69; return ${ERRORCODE}; } \&#xA;        || echo -e &#34;Fatto.\n&#34;&#xA;        &#xA;    [[ $? -gt 0 ]] &amp;&amp; return $?&#xA;}&#xA;&#xA;installlosuite() {&#xA;    cd $(tar -tf LibreOffice&#34;$VERSION&#34;Linuxx86-64rpm.tar.gz|head -1) &amp;&amp; sudo dnf -y localinstall RPMS/ &amp;&amp; cd ..&#xA;    cd $(tar -tf LibreOffice&#34;$VERSION&#34;Linuxx86-64rpmlangpackit.tar.gz|head -1) &amp;&amp; sudo dnf -y localinstall RPMS/ &amp;&amp; cd ..&#xA;    cd $(tar -tf LibreOffice&#34;$VERSION&#34;Linuxx86-64rpmhelppackit.tar.gz|head -1) &amp;&amp; sudo dnf -y localinstall RPMS/ &amp;&amp; cd $HOME&#xA;}&#xA;&#xA;isloupgradeable() {&#xA;    VERSIONEINSTALLATA=$(dnf info installed|grep -A 2 libreoffice|grep Version|head -n 1|cut -d &#34;:&#34; -f 2|cut -c 2-7)&#xA;    VERSIONEONLINE=$(curl https://it.libreoffice.org/download/download/ 2  /dev/null|grep &#34;version=&#34;|head -n 1|cut -d &#34;&amp;&#34; -f 2|cut -d &#34;=&#34; -f 2)&#xA;    echo &#34;VERSIONE INSTALLATA: $VERSIONEINSTALLATA&#34;&#xA;    echo &#34;VERSIONE ONLINE:     $VERSIONEONLINE&#34;&#xA;    [[ ${VERSIONEINSTALLATA} == ${VERSIONEONLINE} ]] \&#xA;        &amp;&amp; echo -e &#34;Nessun aggiornamento disponibile.&#34; \&#xA;        || echo -e &#34;Una nuova versione è disponibile.\n&#34;&#xA;}&#xA;&#xA;cleanup() {&#xA;    rm -r /tmp/tmp.&#xA;}&#xA;&#xA;removeoldlosuite() {&#xA;    sudo dnf -y remove libreoffice&#xA;}&#xA;&#xA;install() {&#xA;    VERSION=$1&#xA;    cd $(mktemp -d)&#xA;    ! downloadlosuite &amp;&amp; { cleanup; exit ${ERRORCODE}; }&#xA;    ! decomprimilosuite &amp;&amp; { cleanup; exit ${ERRORCODE}; }&#xA;    ! removeoldlosuite &amp;&amp; { cleanup; exit ${ERRORCODE}; }&#xA;    installlosuite &amp;&amp; cleanup || exit ${ERRORCODE}?&#xA;}&#xA;&#xA;noparam() {&#xA;    clear;&#xA;    usage&#xA;    exit 0&#xA;}&#xA;&#xA;main() {&#xA;    while getopts &#34;ui:h&#34; opt; do&#xA;        case $opt in&#xA;            u ) islo_upgradeable ;;&#xA;            i ) install $OPTARG ;;&#xA;            h ) clear;usage; exit;;&#xA;            ) clear;echo -e &#34;[ERRORE] Opzione non valida.&#34;;usage; exit 1;;&#xA;        esac&#xA;    done&#xA;    shift $(($OPTIND - 1))&#xA;}&#xA;&#xA;[[ $# -eq 0 ]] &amp;&amp; noparam || main $&#xA;&#xA;#bash #libreoffice #scripting]]&gt;</description>
      <content:encoded><![CDATA[<p><img src="https://pixelfed.uno/storage/m/_v2/489827599091373610/7c649620b-69b6c5/KrPmYlEt1gcy/imPYx0fikTBe14R3C5cAH21rMs6qSEoBGeTujUdT.webp" alt="office"></p>

<p>Piccolo script scritto di fretta per avere sotto mano la versione fresh di Libreoffice su Fedora invece che quella pacchettizzata. Un giorno ci ritornerò come ho fatto con “<a href="https://noblogo.org/aytin/gestione-totp-in-bash" rel="nofollow">Gestione TOTP in bash</a>“
</p>

<pre><code class="language-bash">#!/bin/bash
#download.documentfoundation.org

usage() {
cat&lt;&lt;EOF
Usa come: ./libreoffice-update.sh [options]
dove:
    [options]
        -u verifica l esistenza di una nuova versione
        -i &lt;numero_version&gt; installa la nuova versione di libreoffice
        -h stampa questa pagina di help

ESEMPI:
    VERIFICA CHE UN AGGIORNAMENTO SIA DISPONIBILE
    libreoffice-update -u

    INSTALLA L ULTIMA VERSIONE DI LIBREOFFICE
    libreoffice-update -i

    VISUALIZZA l HELP
    libreoffice-update -h
EOF
}

download_lo_suite() {
    echo -n &#34;Inizio download LibreOffice $VERSION... &#34;
    [[ $(aria2c https://download.documentfoundation.org/libreoffice/stable/&#34;$VERSION&#34;/rpm/x86_64/LibreOffice_&#34;$VERSION&#34;_Linux_x86-64_rpm.tar.gz | grep ERROR) ]] \
        &amp;&amp; { echo &#34;*** Impossibile scaricare LibreOffice $VERSION ***&#34;; ERROR_CODE=64; return ${ERROR_CODE}; } \
        || echo &#34;Fatto.&#34;

    echo -n &#34;Inizio download LibreOfficeLangack $VERSION... &#34;
    [[ $(aria2c https://download.documentfoundation.org/libreoffice/stable/&#34;$VERSION&#34;/rpm/x86_64/LibreOffice_&#34;$VERSION&#34;_Linux_x86-64_rpm_langpack_it.tar.gz | grep ERROR) ]] \
        &amp;&amp; { echo &#34;*** Impossibile scaricare LibreOfficelangPack $VERSION ***&#34;; ERROR_CODE=65; return ${ERROR_CODE}; } \
        || echo &#34;Fatto.&#34;

    echo -n &#34;Inizio download LibreOfficeHelppack $VERSION... &#34;
    [[ $(aria2c https://download.documentfoundation.org/libreoffice/stable/&#34;$VERSION&#34;/rpm/x86_64/LibreOffice_&#34;$VERSION&#34;_Linux_x86-64_rpm_helppack_it.tar.gz | grep ERROR) ]] \
        &amp;&amp; { echo &#34;*** Impossibile scaricare LibreOfficeHelppack $VERSION ***&#34;; ERROR_CODE=66; return ${ERROR_CODE}; } \
        || echo -e &#34;Fatto.\n&#34;
}


decomprimi_lo_suite() {
    echo -n &#34;Decompressione tar LibreOffice $VERSION in corso... &#34;
    [[ $(tar -xzf LibreOffice_&#34;$VERSION&#34;_Linux_x86-64_rpm.tar.gz 2&gt;/dev/null) ]] \
        &amp;&amp; { echo &#34;*** Impossibile decomprimere il tar di LibreOffice $VERSION ***&#34;; ERROR_CODE=67; return ${ERROR_CODE}; } \
        || echo &#34;Fatto.&#34;

    echo -n &#34;Decompressione tar LibreOfficeLangpack $VERSION in corso... &#34;
    [[ $(tar -xzf LibreOffice_&#34;$VERSION&#34;_Linux_x86-64_rpm_langpack_it.tar.gz  2&gt;/dev/null) ]] \
        &amp;&amp; { echo &#34;*** Impossibile decomprimere il tar di LibreOfficeLangpack $VERSION ***&#34;; ERROR_CODE=68; return ${ERROR_CODE}; } \
        || echo &#34;Fatto.&#34;

    echo -n &#34;Decompressione tar LibreOfficeHelppack $VERSION in corso... &#34;
    [[ $(tar -xzf LibreOffice_&#34;$VERSION&#34;_Linux_x86-64_rpm_helppack_it.tar.gz 2&gt;/dev/null) ]] \
        &amp;&amp; { echo &#34;*** Impossibile decomprimere il tar di LibreOfficeHelppack $VERSION ***&#34;; ERROR_CODE=69; return ${ERROR_CODE}; } \
        || echo -e &#34;Fatto.\n&#34;
        
    [[ $? -gt 0 ]] &amp;&amp; return $?
}

install_lo_suite() {
    cd $(tar -tf LibreOffice_&#34;$VERSION&#34;_Linux_x86-64_rpm.tar.gz|head -1) &amp;&amp; sudo dnf -y localinstall RPMS/* &amp;&amp; cd ..
    cd $(tar -tf LibreOffice_&#34;$VERSION&#34;_Linux_x86-64_rpm_langpack_it.tar.gz|head -1) &amp;&amp; sudo dnf -y localinstall RPMS/* &amp;&amp; cd ..
    cd $(tar -tf LibreOffice_&#34;$VERSION&#34;_Linux_x86-64_rpm_helppack_it.tar.gz|head -1) &amp;&amp; sudo dnf -y localinstall RPMS/* &amp;&amp; cd $HOME
}

is_lo_upgradeable() {
    VERSIONE_INSTALLATA=$(dnf info installed|grep -A 2 libreoffice|grep Version|head -n 1|cut -d &#34;:&#34; -f 2|cut -c 2-7)
    VERSIONE_ONLINE=$(curl https://it.libreoffice.org/download/download/ 2&gt;/dev/null|grep &#34;version=&#34;|head -n 1|cut -d &#34;&amp;&#34; -f 2|cut -d &#34;=&#34; -f 2)
    echo &#34;VERSIONE INSTALLATA: $VERSIONE_INSTALLATA&#34;
    echo &#34;VERSIONE ONLINE:     $VERSIONE_ONLINE&#34;
    [[ ${VERSIONE_INSTALLATA} == ${VERSIONE_ONLINE} ]] \
        &amp;&amp; echo -e &#34;Nessun aggiornamento disponibile.&#34; \
        || echo -e &#34;Una nuova versione è disponibile.\n&#34;
}

cleanup() {
    rm -r /tmp/tmp.*
}

remove_old_lo_suite() {
    sudo dnf -y remove libreoffice*
}

install() {
    VERSION=$1
    cd $(mktemp -d)
    ! download_lo_suite &amp;&amp; { cleanup; exit ${ERROR_CODE}; }
    ! decomprimi_lo_suite &amp;&amp; { cleanup; exit ${ERROR_CODE}; }
    ! remove_old_lo_suite &amp;&amp; { cleanup; exit ${ERROR_CODE}; }
    install_lo_suite &amp;&amp; cleanup || exit ${ERROR_CODE}?
}

noparam() {
    clear;
    usage
    exit 0
}

main() {
    while getopts &#34;ui:h&#34; opt; do
        case $opt in
            u ) is_lo_upgradeable ;;
            i ) install $OPTARG ;;
            h ) clear;usage; exit;;
            * ) clear;echo -e &#34;[ERRORE] Opzione non valida.&#34;;usage; exit 1;;
        esac
    done
    shift $(($OPTIND - 1))
}

[[ $# -eq 0 ]] &amp;&amp; noparam || main $*
</code></pre>

<p><a href="/aytin/tag:bash" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">bash</span></a> <a href="/aytin/tag:libreoffice" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">libreoffice</span></a> <a href="/aytin/tag:scripting" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">scripting</span></a></p>
]]></content:encoded>
      <guid>https://noblogo.org/aytin/aggiornare-libreoffice-su-fedora</guid>
      <pubDate>Fri, 23 Feb 2024 14:55:02 +0000</pubDate>
    </item>
    <item>
      <title>Gestione TOTP in bash</title>
      <link>https://noblogo.org/aytin/gestione-totp-in-bash</link>
      <description>&lt;![CDATA[totp&#xA;&#xA;In una giornata in cui avevo un po’ di tempo da investire, ho ripreso la versione 2 dello script &#34;Generazione OTP via bash&#34;, e ho deciso di renderlo un po’ più flessibile introducendo una logica crud. Anzi, il termine corretto dovrebbe essere &#34;dave&#34; (Delete, Add, View, Edit).&#xA;!--more--&#xA;Che altro aggiungere?&#xA;&#xA;Il file è ancora un insieme di coppie chiave-valore (nome account e chiave privata).&#xA;Il separatore è il simbolo &#34;:&#34;. Ma può essere configurato cambiando il valore della variabile &#34;DELIMITER&#34;.&#xA;Lo script è discretamente commentato così, in futuro, riuscirò a ricordare perché ho fatto quello che ho fatto (tipo &#34;Memento&#34;).&#xA;Le opzioni di gpg per la cifratura/decifratura del file sono configurabili nello script modificando il valore delle variabili &#34;GPG\ENC\OPTIONS&#34; e &#34;GPG\DEC\OPTIONS&#34;.&#xA;L’uso di getopts, mi rendo conto, è solo un esercizio di stile. Non è molto utile per come l’ho usato, visto che ogni optarg corrisponde ad un’operazione autoconclusiva.&#xA;Ero partito per rimpiazzare ogni costrutto if con combinazioni di lista and / lista or ma le condizioni risultavano talmente complesse da rendere il codice francamente molto più incomprensibile di adesso. In alternativa avrei potuto delegare l’esecuzione delle condizioni ad un insieme di funzioni con una stratificazione tale da ricadere nel punto precedente: offuscamento permanente del codice che va a vanificare l’intenzione iniziale di semplificare il codice e renderlo più leggibile.&#xA;&#xA;!/usr/local/bin/bash&#xA;&#xA;init() {&#xA;    # CODICI D&#39;ERRORE&#xA;    DECRYPTERR=64&#xA;    INVALIDOPTIONERR=65&#xA;    INVALIDACCOUNTERR=66&#xA;    ACCOUNTNOTFOUNDERR=67&#xA;    ACCOUNTNAMEEMPTYERR=68&#xA;    ACCOUNTEXISTSERR=69&#xA;    INVALIDKEYERR=70&#xA;&#xA;    # Colorscheme&#xA;    LIGHTGREEN=&#39;\033[1;32m&#39;&#xA;    LIGHTRED=&#39;\033[1;31m&#39;&#xA;    NC=&#39;\033[0m&#39;&#xA;&#xA;    # Path del file cifrato contentente i codici 2FA&#xA;    KEYS2FAFILE=&#34;path/fileenc.gpg&#34;&#xA;&#xA;    # Separatore delle coppie chiave-valore&#xA;    DELIMITER=&#34;:&#34;&#xA;&#xA;    # path gpg e opzioni di cifratura/decifratura&#xA;    GPGTTY=$(tty)&#xA;    export GPGTTY&#xA;&#xA;    #GPGCOMMAND=&#34;/usr/local/bin/gpg&#34;&#xA;    GPGENCOPTIONS=&#34;--yes -c --s2k-cipher-algo aes256 --s2k-digest-algo sha512 --s2k-mode 3 --s2k-count 260000 - &#34;&#xA;    GPGDECOPTIONS=&#39;-d&#39;&#xA;&#xA;    # Caratteri consentiti nel nome account&#xA;    PATTERN=&#39;^[a-zA-Z0-9.-]+$&#39;;&#xA;&#xA;    # Su MacOS è disponibile &#34;pbcopy&#34;, su nix &#34;xsel&#34;&#xA;    case &#34;$(sysctl hw.targettype 2  /dev/null)&#34; in&#xA;        &#34;&#34;)&#xA;            CLIPBOARD=&#34;xsel -bi&#34;&#xA;            GPGCOMMAND=&#34;/usr/bin/gpg&#34;;;&#xA;         )&#xA;            CLIPBOARD=&#34;pbcopy&#34;&#xA;            GPGCOMMAND=&#34;/usr/local/bin/gpg&#34;;;&#xA;    esac&#xA;&#xA;    # Decifra e carica in ram il contenuto del file cifrato.&#xA;    echo &#34;Decifratura del file degli account in corso...&#34;&#xA;    KEYS2FAFILEDEC=$(${GPGCOMMAND} ${GPGDECOPTIONS} &#34;${KEYS2FAFILE}&#34; 2  /dev/null)&#xA;&#xA;    # Se la password non è corretta, restituisce un errore.&#xA;    [[ -z &#34;${KEYS2FAFILEDEC}&#34; ]] &amp;&amp; { errmsg &#34;${DECRYPTERR}&#34; 1; }&#xA;    infomsg &#34;Fatto.&#34;&#xA;}&#xA;&#xA;getaccount() {&#xA;    ACCOUNTSTDIO=&#34;$1&#34;&#xA;&#xA;    # Check nome account vuoto&#xA;    [[ -z &#34;${ACCOUNTSTDIO}&#34; ]] &amp;&amp; return &#34;${ACCOUNTNAMEEMPTYERR}&#34;&#xA;&#xA;    # Check caratteri invalidi nel nome account&#xA;    [[ ! &#34;${ACCOUNTSTDIO}&#34; =~ $PATTERN ]] &amp;&amp; return &#34;${INVALIDACCOUNTERR}&#34;&#xA;&#xA;    # Estrae l&#39;elemento contenente l&#39;id dato in input&#xA;    OTPLINE=$(echo &#34;${KEYS2FAFILEDEC}&#34; | grep &#34;${ACCOUNTSTDIO}:&#34;)&#xA;&#xA;    # Se l&#39;account non esiste, restituisce un errore&#xA;    [[ -z &#34;${OTPLINE}&#34; ]] &amp;&amp; return &#34;${ACCOUNTNOTFOUNDERR}&#34;&#xA;&#xA;    ACCOUNT=$(echo $OTPLINE | cut -d&#34;${DELIMITER}&#34; -f 1)&#xA;    KEY=$(echo $OTPLINE | cut -d&#34;${DELIMITER}&#34; -f 2)&#xA;}&#xA;&#xA;cryptaccountfile() {&#xA;    ACCOUNTFILE=&#34;$1&#34;&#xA;    echo -e &#34;\nCifratura del file degli account in corso...&#34;&#xA;    ${GPGCOMMAND} -o &#34;$KEYS2FAFILE&#34; ${GPGENCOPTIONS} &lt;&lt;&lt; $(echo -e &#34;${ACCOUNTFILE}&#34;)&#xA;}&#xA;&#xA;noparam() {&#xA;    clear&#xA;    help&#xA;    exit 0&#xA;}&#xA;&#xA;Visualizza messaggi di errore su stdio&#xA;errmsg() {&#xA;    if [[ $# -ne 2 ]]; then&#xA;        echo -r&#34;${LIGHTRED}[DEBUG] [errmsg] Numero di parametri non corretto.${NC}.&#34;; exit 1&#xA;    else&#xA;        case $1 in&#xA;            &#34;${DECRYPTERR}&#34;            ) echo -e &#34;${LIGHTRED}[ERRORE] Impossibile decriptare il file.${NC}&#34;;;&#xA;            &#34;${INVALIDOPTIONERR}&#34;     ) echo -e &#34;${LIGHTRED}[ERRORE] Opzione \&#34;$OPTARG\&#34; non valida.${NC}&#34;;;&#xA;            &#34;${INVALIDACCOUNTERR}&#34;    ) echo -e &#34;${LIGHTRED}ERRORE] Rilevati caratteri non consentiti.\nI caratteri ammessi sono: [a-z[0-9].-${NC}&#34;;;&#xA;            &#34;${ACCOUNTNOTFOUNDERR}&#34;  ) echo -e &#34;${LIGHTRED}[ERRORE] Account non trovato${NC}&#34;;;&#xA;            &#34;${ACCOUNTNAMEEMPTYERR}&#34; ) echo -e &#34;${LIGHTRED}[ERRORE] Nome account vuoto.${NC}&#34;;;&#xA;            &#34;${ACCOUNTEXISTSERR}&#34;     ) echo -e &#34;${LIGHTRED}[ERRORE] Nome account già esistente${NC}&#34;;;&#xA;            &#34;${INVALIDKEYERR}&#34;        ) echo -e &#34;${LIGHTRED}[ERRORE] Chiave privata nulla o non corretta.\nLa chiave privata deve essere base32.\n${NC}&#34;;;&#xA;            ) echo -e &#34;${LIGHTRED}[ERRORE] Errore generico.${NC}&#34;;;&#xA;        esac&#xA;&#xA;        [[ $2 -eq 1 ]] &amp;&amp; exit $2;&#xA;    fi&#xA;}&#xA;&#xA;Visualizza messaggi info su stdio&#xA;infomsg() {&#xA;    echo -e &#34;${LIGHTGREEN}$1${NC}&#34;&#xA;}&#xA;&#xA;continuemsg() {&#xA;    case $1 in&#xA;        &#34;insert&#34;    ) ACTION=&#34;creato&#34;;;&#xA;        &#34;edit&#34;      ) ACTION=&#34;modificato&#34;;;&#xA;        &#34;delete&#34;    ) ACTION=&#34;cancellato&#34;;;&#xA;    esac&#xA;&#xA;    echo -n &#34;L&#39;account \&#34;${ACCOUNT}\&#34; sta per essere ${ACTION}. Vuoi continuare? [y|N]: &#34;; read ANSWER;&#xA;}&#xA;&#xA;continueyn() {&#xA;        ANSWER=&#34;0&#34;&#xA;        while [[ &#34;${ANSWER}&#34; != &#34;y&#34; ]]; do&#xA;            [[ -z &#34;${ANSWER}&#34; || &#34;${ANSWER}&#34; == &#34;N&#34; ]] &amp;&amp; { echo &#34;Operazione annullata&#34;; exit; }&#xA;            [[ &#34;${ANSWER}&#34; != &#34;y&#34; || &#34;${ANSWER}&#34; != &#34;N&#34; ]] &amp;&amp; continuemsg &#34;$1&#34;&#xA;        done&#xA;}&#xA;&#xA;totp() {&#xA;    # Se la&#39;ccount non esiste o è invalido, esco con errore&#xA;    getaccount &#34;$1&#34;; RETCODE=$?&#xA;    case &#34;${RETCODE}&#34; in&#xA;        &#34;${ACCOUNTNOTFOUNDERR}&#34;  ) errmsg &#34;${ACCOUNTNOTFOUNDERR}&#34; 1;;&#xA;        &#34;${INVALIDACCOUNTERR}&#34;    ) errmsg &#34;${INVALIDACCOUNTERR}&#34; 1;;&#xA;    esac&#xA;&#xA;    echo -e &#34;\nCopia OTP nella clipboard...&#34;&#xA;    # Calcola l&#39;otp e lo trasferisce nella clipboard con xsel&#xA;    oathtool --totp -b &#34;${KEY}&#34; | $CLIPBOARD&#xA;    infomsg &#34;Fatto.&#34;&#xA;    exit&#xA;}&#xA;&#xA;insert() {&#xA;    # Se l&#39;account non esiste o è invalido, esco con errore&#xA;    getaccount &#34;$1&#34;; RETCODE=$?&#xA;    case &#34;${RETCODE}&#34; in&#xA;        0                           ) errmsg &#34;${ACCOUNTEXISTSERR}&#34; 1;;&#xA;        &#34;${INVALIDACCOUNTERR}&#34;    ) errmsg &#34;${INVALIDACCOUNTERR}&#34; 1;;&#xA;    esac&#xA;&#xA;    ACCOUNT=&#34;$1&#34;&#xA;&#xA;    # Inserisci la chiave&#xA;    echo -en &#34;Nuovo account: ${ACCOUNT}\nInserisci la nuova chiave privata: &#34;; read KEY&#xA;    while [[ -z &#34;${KEY}&#34; || ! $(echo ${KEY} | oathtool -b - 2  /dev/null) ]]; do&#xA;        errmsg &#34;${INVALIDKEYERR}&#34; 0&#xA;        echo -n &#34;Inserisci la chiave privata: &#34;; read KEY&#xA;    done&#xA;&#xA;    infomsg &#34;\nNuovo account:        ${ACCOUNT}\nNuova chiave privata: ${KEY}&#34;&#xA;    continueyn &#34;insert&#34;&#xA;&#xA;    # Appendo il nuovo record nel file degli account&#xA;    KEYS2FAFILEDEC+=&#34;\n${ACCOUNT}${DELIMITER}${KEY}&#34;&#xA;    infomsg &#34;L&#39;account \&#34;${ACCOUNT}\&#34; è stato creato con successo.&#34;&#xA;&#xA;    # Cifratura del file degli account&#xA;    cryptaccountfile &#34;${KEYS2FAFILEDEC}&#34;&#xA;    infomsg &#34;Fatto.&#34;&#xA;    exit&#xA;}&#xA;&#xA;edit() {&#xA;    # Se la&#39;ccount non esiste o è invalido, esco con errore&#xA;    getaccount &#34;$1&#34;; RETCODE=$?&#xA;    case &#34;${RETCODE}&#34; in&#xA;        &#34;${ACCOUNTNOTFOUNDERR}&#34;  ) errmsg &#34;${ACCOUNTNOTFOUNDERR}&#34; 1;;&#xA;        &#34;${INVALIDACCOUNTERR}&#34;    ) errmsg &#34;${INVALIDACCOUNTERR}&#34; 1;;&#xA;    esac&#xA;&#xA;    # Faccio una copia dell&#39;account per un eventuale ripristino durante&#xA;    # la fase di input&#xA;    ACCOUNTTEMP=&#34;${ACCOUNT}&#34;&#xA;    ACCOUNTMOD=&#34;${ACCOUNT}&#34;&#xA;&#xA;    # Inserisco il nuovo nome account.&#xA;    # 1. Se è vuoto: è la conferma dell&#39;account inserito all&#39;inizio e proseguo&#xA;    # 2. Se il nome è invalido: sollevo un&#39;eccezione e reitero&#xA;    # 3. Se il nome è valido:&#xA;    #    a. Se è un nome nuovo: acquisisco il nuovo nome, recupero nome e chiave dell&#39;account di partenza e proseguo&#xA;    #    b. Sè è lo stesso nome di partenza: è la conferma dell&#39;account iniziale e proseguo&#xA;    #    c. Se è un altro nome esistente o invalido: come il punto 2.&#xA;    while true; do&#xA;        echo&#xA;        echo -en &#34;Account: ${ACCOUNT}\nInserisci nuovo nome account: &#34;; read ACCOUNTMOD;&#xA;        getaccount &#34;${ACCOUNTMOD}&#34;; RETCODE=$?&#xA;        case &#34;${RETCODE}&#34; in&#xA;            # Punto 1.&#xA;            &#34;${ACCOUNTNAMEEMPTYERR}&#34; ) ACCOUNTMOD=&#34;${ACCOUNT}&#34;; break;;&#xA;            # Punto 2&#xA;            &#34;${INVALIDACCOUNTERR}&#34;    ) errmsg &#34;${INVALIDACCOUNTERR}&#34; 0;;&#xA;            # Punto 3a.&#xA;            &#34;${ACCOUNTNOTFOUNDERR}&#34;  ) getaccount &#34;${ACCOUNTTEMP}&#34;; break;;&#xA;            # Punti 3b e 3c, rispettivamente&#xA;            0                           ) [[ &#34;${ACCOUNTMOD}&#34; == &#34;${ACCOUNTTEMP}&#34; ]] &amp;&amp; break \&#xA;                                                                                 || { getaccount &#34;${ACCOUNTTEMP}&#34;; errmsg &#34;${ACCOUNTEXISTSERR}&#34; 0; };;&#xA;        esac&#xA;    done&#xA;&#xA;    [[ &#34;${ACCOUNT}&#34; != &#34;${ACCOUNTMOD}&#34; &amp;&amp; ! -z &#34;${ACCOUNTMOD}&#34; ]] &amp;&amp; msg=&#34;\nL&#39;account \&#34;${ACCOUNT}\&#34; è stato modificato in \&#34;${ACCOUNTMOD}\&#34;.&#34; \&#xA;                                                                    || msg=&#34;\nNessuna modifica rilevata.\n&#34;&#xA;    infomsg &#34;${msg}&#34;&#xA;&#xA;    # Modifica la chiave privata&#xA;    KEYMOD=&#34;${KEY}&#34;&#xA;    echo&#xA;&#xA;    while true; do&#xA;        echo -en &#34;Chiave privata: ${KEY}\nInserisci la nuova chiave privata: &#34;; read KEYMOD&#xA;        if [[ -z $KEYMOD ]]; then&#xA;            KEYMOD=&#34;${KEY}&#34;; break&#xA;        elif [[ ! $(echo &#34;${KEYMOD}&#34; | oathtool -b - 2  /dev/null) ]]; then&#xA;            errmsg &#34;${INVALIDKEYERR}&#34; 0; KEYMOD=&#34;${KEY}&#34;&#xA;        else&#xA;            break&#xA;        fi&#xA;    done&#xA;&#xA;    # Se c&#39;è stata almeno una modifica (account/key), sovrascrivo il record. Altrimenti esco senza fare nulla.&#xA;    if [[ &#34;${ACCOUNT}&#34; != &#34;${ACCOUNTMOD}&#34; || &#34;${KEY}&#34; != &#34;${KEYMOD}&#34; ]]; then&#xA;        # Conferma modifica&#xA;&#xA;        infomsg &#34;\nAccount:              ${ACCOUNT}\nChiave privata:       ${KEY}\nNuovo account:        ${ACCOUNTMOD}\nNuova chiave privata: ${KEYMOD}\n&#34;&#xA;        continueyn &#34;edit&#34;&#xA;&#xA;        # Modifica del file degli account&#xA;        KEYS2FAFILEDECTEMP=$(sed &#34;s/${ACCOUNT}${DELIMITER}${KEY}/${ACCOUNTMOD}${DELIMITER}${KEYMOD}/&#34; &lt;&lt;&lt; &#34;${KEYS2FAFILEDEC}&#34;)&#xA;        infomsg &#34;L&#39;account \&#34;${ACCOUNT}\&#34; è stato modificato con successo.&#34;&#xA;&#xA;        # Cifratura del file degli account&#xA;        cryptaccountfile &#34;${KEYS2FAFILEDECTEMP}&#34;&#xA;    else&#xA;        infomsg &#34;Nessuna modifica rilevata.&#34;&#xA;    fi&#xA;    infomsg &#34;Fatto.&#34;&#xA;}&#xA;&#xA;delete() {&#xA;    if getaccount &#34;$1&#34;; then&#xA;        # Conferma eliminazione&#xA;        infomsg &#34;\nAccount:              ${ACCOUNT}\nChiave privata:       ${KEY}\n&#34;&#xA;        continueyn &#34;delete&#34;&#xA;&#xA;        # Cancellazione account&#xA;        KEYS2FAFILEDECTEMP=$(sed &#34;/$ACCOUNT/d&#34; &lt;&lt;&lt; &#34;${KEYS2FAFILEDEC}&#34;)&#xA;        infomsg &#34;L&#39;account \&#34;$ACCOUNT\&#34; è stato eliminato.&#34;&#xA;&#xA;        # Cifratura del nuovo file degli account&#xA;        cryptaccountfile &#34;${KEYS2FAFILEDECTEMP}&#34;&#xA;        infomsg &#34;Fatto.&#34;&#xA;    else&#xA;        errmsg &#34;${ACCOUNTNOTFOUNDERR}&#34; 1&#xA;    fi&#xA;}&#xA;&#xA;list() {&#xA;    echo&#xA;    while read LINE; do&#xA;        cut -d&#34;${DELIMITER}&#34; -f 1 &lt;&lt;&lt; &#34;${LINE}&#34;&#xA;    done &lt;&lt;&lt; &#34;${KEYS2FAFILEDEC}&#34;&#xA;}&#xA;&#xA;help() {&#xA;cat&lt;&lt;EOF&#xA;Usa come: ./totp.sh [options] ARG&#xA;dove:&#xA;    [options]&#xA;        -l restituisce la lista degli account.&#xA;        -t nomeaccount restituisce l&#39;otp per quell&#39;account.&#xA;        -i nomeaccount inserisce un nuovo account.&#xA;        -d nomeaccount cancella nomeaccount.&#xA;        -e nomeaccount modifica nomeaccount.&#xA;        -h stampa questa pagina di help.&#xA;&#xA;ESEMPI:&#xA;    RESTITUISCE LA LISTA DEGLI ACCOUNT&#xA;    totp.sh -l&#xA;&#xA;    INSERISCE UN ACCOUNT&#xA;    totp.sh -i dropbox&#xA;&#xA;    CANCELLA UN ACCOUNT&#xA;    totp.sh -d megaupload&#xA;&#xA;    MODIFICA UN ACCOUNT&#xA;    totp.sh -e paypal&#xA;&#xA;    GENERA OTP&#xA;    totp.sh -t firefoxsync&#xA;EOF&#xA;}&#xA;&#xA;main() {&#xA;    init&#xA;    while getopts &#34;lt:i:e:d:&#34; opt; do&#xA;        case $opt in&#xA;            l ) list ;;&#xA;            t ) totp $OPTARG ;;&#xA;            i ) insert $OPTARG ;;&#xA;            e ) edit $OPTARG ;;&#xA;            d ) delete $OPTARG ;;&#xA;            ) clear;errmsg &#34;${INVALIDOPTIONERR}&#34; 1;;&#xA;        esac&#xA;    done&#xA;    shift $(($OPTIND - 1))&#xA;}&#xA;&#xA;[[ $# -eq 0 || $# -eq 1 &amp;&amp; &#34;$1&#34; == &#34;-h&#34; ]] &amp;&amp; noparam || main $*&#xA;&#xA;#bash #totp #cryptography #gpg #scripting]]&gt;</description>
      <content:encoded><![CDATA[<p><img src="https://pixelfed.uno/storage/m/_v2/489827599091373610/7c649620b-69b6c5/x5Ykru5oywKu/RE9KMTKwgIzhwDrkGnZqsTThRVvDSlNlyB2XwSv4.webp" alt="totp"></p>

<p>In una giornata in cui avevo un po’ di tempo da investire, ho ripreso la versione 2 dello script “<a href="https://noblogo.org/aytin/generazione-otp-via-bash" rel="nofollow">Generazione OTP via bash</a>”, e ho deciso di renderlo un po’ più flessibile introducendo una logica <strong>crud</strong>. Anzi, il termine corretto dovrebbe essere “<strong>dave</strong>” (<strong>D</strong>elete, <strong>A</strong>dd, <strong>V</strong>iew, <strong>E</strong>dit).

Che altro aggiungere?</p>
<ul><li>Il file è ancora un insieme di coppie chiave-valore (nome account e chiave privata).</li>
<li>Il separatore è il simbolo “<strong>:</strong>”. Ma può essere configurato cambiando il valore della variabile “<strong>DELIMITER</strong>”.</li>
<li>Lo script è discretamente commentato così, in futuro, riuscirò a ricordare perché ho fatto quello che ho fatto (tipo “Memento”).</li>
<li>Le opzioni di gpg per la cifratura/decifratura del file sono configurabili nello script modificando il valore delle variabili “<strong>GPG_ENC_OPTIONS</strong>” e “<strong>GPG_DEC_OPTIONS</strong>”.</li>
<li>L’uso di <strong>getopts,</strong> mi rendo conto, è solo un esercizio di stile. Non è molto utile per come l’ho usato, visto che ogni optarg corrisponde ad un’operazione autoconclusiva.</li>
<li>Ero partito per rimpiazzare ogni costrutto <strong>if</strong> con combinazioni di lista and / lista or ma le condizioni risultavano talmente complesse da rendere il codice francamente molto più incomprensibile di adesso. In alternativa avrei potuto delegare l’esecuzione delle condizioni ad un insieme di funzioni con una stratificazione tale da ricadere nel punto precedente: offuscamento permanente del codice che va a vanificare l’intenzione iniziale di semplificare il codice e renderlo più leggibile.</li></ul>

<pre><code class="language-bash">
#!/usr/local/bin/bash


init() {
    # CODICI D&#39;ERRORE
    DECRYPT_ERR=64
    INVALID_OPTION_ERR=65
    INVALID_ACCOUNT_ERR=66
    ACCOUNT_NOT_FOUND_ERR=67
    ACCOUNT_NAME_EMPTY_ERR=68
    ACCOUNT_EXISTS_ERR=69
    INVALID_KEY_ERR=70

    # Colorscheme
    LIGHT_GREEN=&#39;\033[1;32m&#39;
    LIGHT_RED=&#39;\033[1;31m&#39;
    NC=&#39;\033[0m&#39;

    # Path del file cifrato contentente i codici 2FA
    KEYS_2FA_FILE=&#34;&lt;path/file_enc.gpg&gt;&#34;

    # Separatore delle coppie chiave-valore
    DELIMITER=&#34;:&#34;

    # path gpg e opzioni di cifratura/decifratura
    GPG_TTY=$(tty)
    export GPG_TTY

    #GPG_COMMAND=&#34;/usr/local/bin/gpg&#34;
    GPG_ENC_OPTIONS=&#34;--yes -c --s2k-cipher-algo aes256 --s2k-digest-algo sha512 --s2k-mode 3 --s2k-count 260000 - &#34;
    GPG_DEC_OPTIONS=&#39;-d&#39;

    # Caratteri consentiti nel nome account
    PATTERN=&#39;^[a-zA-Z0-9._-]+$&#39;;

    # Su MacOS è disponibile &#34;pbcopy&#34;, su *nix &#34;xsel&#34;
    case &#34;$(sysctl hw.targettype 2&gt;/dev/null)&#34; in
        &#34;&#34;)
            CLIPBOARD=&#34;xsel -bi&#34;
            GPG_COMMAND=&#34;/usr/bin/gpg&#34;;;
         *)
            CLIPBOARD=&#34;pbcopy&#34;
            GPG_COMMAND=&#34;/usr/local/bin/gpg&#34;;;
    esac

    # Decifra e carica in ram il contenuto del file cifrato.
    echo &#34;Decifratura del file degli account in corso...&#34;
    KEYS_2FA_FILE_DEC=$(${GPG_COMMAND} ${GPG_DEC_OPTIONS} &#34;${KEYS_2FA_FILE}&#34; 2&gt;/dev/null)

    # Se la password non è corretta, restituisce un errore.
    [[ -z &#34;${KEYS_2FA_FILE_DEC}&#34; ]] &amp;&amp; { err_msg &#34;${DECRYPT_ERR}&#34; 1; }
    info_msg &#34;Fatto.&#34;
}


get_account() {
    ACCOUNT_STDIO=&#34;$1&#34;

    # Check nome account vuoto
    [[ -z &#34;${ACCOUNT_STDIO}&#34; ]] &amp;&amp; return &#34;${ACCOUNT_NAME_EMPTY_ERR}&#34;

    # Check caratteri invalidi nel nome account
    [[ ! &#34;${ACCOUNT_STDIO}&#34; =~ $PATTERN ]] &amp;&amp; return &#34;${INVALID_ACCOUNT_ERR}&#34;

    # Estrae l&#39;elemento contenente l&#39;id dato in input
    OTP_LINE=$(echo &#34;${KEYS_2FA_FILE_DEC}&#34; | grep &#34;${ACCOUNT_STDIO}:&#34;)

    # Se l&#39;account non esiste, restituisce un errore
    [[ -z &#34;${OTP_LINE}&#34; ]] &amp;&amp; return &#34;${ACCOUNT_NOT_FOUND_ERR}&#34;

    ACCOUNT=$(echo $OTP_LINE | cut -d&#34;${DELIMITER}&#34; -f 1)
    KEY=$(echo $OTP_LINE | cut -d&#34;${DELIMITER}&#34; -f 2)
}


crypt_account_file() {
    ACCOUNT_FILE=&#34;$1&#34;
    echo -e &#34;\nCifratura del file degli account in corso...&#34;
    ${GPG_COMMAND} -o &#34;$KEYS_2FA_FILE&#34; ${GPG_ENC_OPTIONS} &lt;&lt;&lt; $(echo -e &#34;${ACCOUNT_FILE}&#34;)
}


no_param() {
    clear
    help
    exit 0
}


# Visualizza messaggi di errore su stdio
err_msg() {
    if [[ $# -ne 2 ]]; then
        echo -r&#34;${LIGHT_RED}[DEBUG] [err_msg] Numero di parametri non corretto.${NC}.&#34;; exit 1
    else
        case $1 in
            &#34;${DECRYPT_ERR}&#34;            ) echo -e &#34;${LIGHT_RED}[ERRORE] Impossibile decriptare il file.${NC}&#34;;;
            &#34;${INVALID_OPTION_ERR}&#34;     ) echo -e &#34;${LIGHT_RED}[ERRORE] Opzione \&#34;$OPTARG\&#34; non valida.${NC}&#34;;;
            &#34;${INVALID_ACCOUNT_ERR}&#34;    ) echo -e &#34;${LIGHT_RED}[ERRORE] Rilevati caratteri non consentiti.\nI caratteri ammessi sono: [a-z][A-Z][0-9].-_${NC}&#34;;;
            &#34;${ACCOUNT_NOT_FOUND_ERR}&#34;  ) echo -e &#34;${LIGHT_RED}[ERRORE] Account non trovato${NC}&#34;;;
            &#34;${ACCOUNT_NAME_EMPTY_ERR}&#34; ) echo -e &#34;${LIGHT_RED}[ERRORE] Nome account vuoto.${NC}&#34;;;
            &#34;${ACCOUNT_EXISTS_ERR}&#34;     ) echo -e &#34;${LIGHT_RED}[ERRORE] Nome account già esistente${NC}&#34;;;
            &#34;${INVALID_KEY_ERR}&#34;        ) echo -e &#34;${LIGHT_RED}[ERRORE] Chiave privata nulla o non corretta.\nLa chiave privata deve essere base32.\n${NC}&#34;;;
            *                           ) echo -e &#34;${LIGHT_RED}[ERRORE] Errore generico.${NC}&#34;;;
        esac

        [[ $2 -eq 1 ]] &amp;&amp; exit $2;
    fi
}


# Visualizza messaggi info su stdio
info_msg() {
    echo -e &#34;${LIGHT_GREEN}$1${NC}&#34;
}


continue_msg() {
    case $1 in
        &#34;insert&#34;    ) ACTION=&#34;creato&#34;;;
        &#34;edit&#34;      ) ACTION=&#34;modificato&#34;;;
        &#34;delete&#34;    ) ACTION=&#34;cancellato&#34;;;
    esac

    echo -n &#34;L&#39;account \&#34;${ACCOUNT}\&#34; sta per essere ${ACTION}. Vuoi continuare? [y|N]: &#34;; read ANSWER;
}


continue_yn() {
        ANSWER=&#34;0&#34;
        while [[ &#34;${ANSWER}&#34; != &#34;y&#34; ]]; do
            [[ -z &#34;${ANSWER}&#34; || &#34;${ANSWER}&#34; == &#34;N&#34; ]] &amp;&amp; { echo &#34;Operazione annullata&#34;; exit; }
            [[ &#34;${ANSWER}&#34; != &#34;y&#34; || &#34;${ANSWER}&#34; != &#34;N&#34; ]] &amp;&amp; continue_msg &#34;$1&#34;
        done
}


totp() {
    # Se la&#39;ccount non esiste o è invalido, esco con errore
    get_account &#34;$1&#34;; RET_CODE=$?
    case &#34;${RET_CODE}&#34; in
        &#34;${ACCOUNT_NOT_FOUND_ERR}&#34;  ) err_msg &#34;${ACCOUNT_NOT_FOUND_ERR}&#34; 1;;
        &#34;${INVALID_ACCOUNT_ERR}&#34;    ) err_msg &#34;${INVALID_ACCOUNT_ERR}&#34; 1;;
    esac

    echo -e &#34;\nCopia OTP nella clipboard...&#34;
    # Calcola l&#39;otp e lo trasferisce nella clipboard con xsel
    oathtool --totp -b &#34;${KEY}&#34; | $CLIPBOARD
    info_msg &#34;Fatto.&#34;
    exit
}


insert() {
    # Se l&#39;account non esiste o è invalido, esco con errore
    get_account &#34;$1&#34;; RET_CODE=$?
    case &#34;${RET_CODE}&#34; in
        0                           ) err_msg &#34;${ACCOUNT_EXISTS_ERR}&#34; 1;;
        &#34;${INVALID_ACCOUNT_ERR}&#34;    ) err_msg &#34;${INVALID_ACCOUNT_ERR}&#34; 1;;
    esac

    ACCOUNT=&#34;$1&#34;

    # Inserisci la chiave
    echo -en &#34;Nuovo account: ${ACCOUNT}\nInserisci la nuova chiave privata: &#34;; read KEY
    while [[ -z &#34;${KEY}&#34; || ! $(echo ${KEY} | oathtool -b - 2&gt;/dev/null) ]]; do
        err_msg &#34;${INVALID_KEY_ERR}&#34; 0
        echo -n &#34;Inserisci la chiave privata: &#34;; read KEY
    done

    info_msg &#34;\nNuovo account:        ${ACCOUNT}\nNuova chiave privata: ${KEY}&#34;
    continue_yn &#34;insert&#34;

    # Appendo il nuovo record nel file degli account
    KEYS_2FA_FILE_DEC+=&#34;\n${ACCOUNT}${DELIMITER}${KEY}&#34;
    info_msg &#34;L&#39;account \&#34;${ACCOUNT}\&#34; è stato creato con successo.&#34;

    # Cifratura del file degli account
    crypt_account_file &#34;${KEYS_2FA_FILE_DEC}&#34;
    info_msg &#34;Fatto.&#34;
    exit
}


edit() {
    # Se la&#39;ccount non esiste o è invalido, esco con errore
    get_account &#34;$1&#34;; RET_CODE=$?
    case &#34;${RET_CODE}&#34; in
        &#34;${ACCOUNT_NOT_FOUND_ERR}&#34;  ) err_msg &#34;${ACCOUNT_NOT_FOUND_ERR}&#34; 1;;
        &#34;${INVALID_ACCOUNT_ERR}&#34;    ) err_msg &#34;${INVALID_ACCOUNT_ERR}&#34; 1;;
    esac

    # Faccio una copia dell&#39;account per un eventuale ripristino durante
    # la fase di input
    ACCOUNT_TEMP=&#34;${ACCOUNT}&#34;
    ACCOUNT_MOD=&#34;${ACCOUNT}&#34;

    # Inserisco il nuovo nome account.
    # 1. Se è vuoto: è la conferma dell&#39;account inserito all&#39;inizio e proseguo
    # 2. Se il nome è invalido: sollevo un&#39;eccezione e reitero
    # 3. Se il nome è valido:
    #    a. Se è un nome nuovo: acquisisco il nuovo nome, recupero nome e chiave dell&#39;account di partenza e proseguo
    #    b. Sè è lo stesso nome di partenza: è la conferma dell&#39;account iniziale e proseguo
    #    c. Se è un altro nome esistente o invalido: come il punto 2.
    while true; do
        echo
        echo -en &#34;Account: ${ACCOUNT}\nInserisci nuovo nome account: &#34;; read ACCOUNT_MOD;
        get_account &#34;${ACCOUNT_MOD}&#34;; RET_CODE=$?
        case &#34;${RET_CODE}&#34; in
            # Punto 1.
            &#34;${ACCOUNT_NAME_EMPTY_ERR}&#34; ) ACCOUNT_MOD=&#34;${ACCOUNT}&#34;; break;;
            # Punto 2
            &#34;${INVALID_ACCOUNT_ERR}&#34;    ) err_msg &#34;${INVALID_ACCOUNT_ERR}&#34; 0;;
            # Punto 3a.
            &#34;${ACCOUNT_NOT_FOUND_ERR}&#34;  ) get_account &#34;${ACCOUNT_TEMP}&#34;; break;;
            # Punti 3b e 3c, rispettivamente
            0                           ) [[ &#34;${ACCOUNT_MOD}&#34; == &#34;${ACCOUNT_TEMP}&#34; ]] &amp;&amp; break \
                                                                                 || { get_account &#34;${ACCOUNT_TEMP}&#34;; err_msg &#34;${ACCOUNT_EXISTS_ERR}&#34; 0; };;
        esac
    done

    [[ &#34;${ACCOUNT}&#34; != &#34;${ACCOUNT_MOD}&#34; &amp;&amp; ! -z &#34;${ACCOUNT_MOD}&#34; ]] &amp;&amp; msg=&#34;\nL&#39;account \&#34;${ACCOUNT}\&#34; è stato modificato in \&#34;${ACCOUNT_MOD}\&#34;.&#34; \
                                                                    || msg=&#34;\nNessuna modifica rilevata.\n&#34;
    info_msg &#34;${msg}&#34;

    # Modifica la chiave privata
    KEY_MOD=&#34;${KEY}&#34;
    echo

    while true; do
        echo -en &#34;Chiave privata: ${KEY}\nInserisci la nuova chiave privata: &#34;; read KEY_MOD
        if [[ -z $KEY_MOD ]]; then
            KEY_MOD=&#34;${KEY}&#34;; break
        elif [[ ! $(echo &#34;${KEY_MOD}&#34; | oathtool -b - 2&gt;/dev/null) ]]; then
            err_msg &#34;${INVALID_KEY_ERR}&#34; 0; KEY_MOD=&#34;${KEY}&#34;
        else
            break
        fi
    done

    # Se c&#39;è stata almeno una modifica (account/key), sovrascrivo il record. Altrimenti esco senza fare nulla.
    if [[ &#34;${ACCOUNT}&#34; != &#34;${ACCOUNT_MOD}&#34; || &#34;${KEY}&#34; != &#34;${KEY_MOD}&#34; ]]; then
        # Conferma modifica

        info_msg &#34;\nAccount:              ${ACCOUNT}\nChiave privata:       ${KEY}\nNuovo account:        ${ACCOUNT_MOD}\nNuova chiave privata: ${KEY_MOD}\n&#34;
        continue_yn &#34;edit&#34;

        # Modifica del file degli account
        KEYS_2FA_FILE_DEC_TEMP=$(sed &#34;s/${ACCOUNT}${DELIMITER}${KEY}/${ACCOUNT_MOD}${DELIMITER}${KEY_MOD}/&#34; &lt;&lt;&lt; &#34;${KEYS_2FA_FILE_DEC}&#34;)
        info_msg &#34;L&#39;account \&#34;${ACCOUNT}\&#34; è stato modificato con successo.&#34;

        # Cifratura del file degli account
        crypt_account_file &#34;${KEYS_2FA_FILE_DEC_TEMP}&#34;
    else
        info_msg &#34;Nessuna modifica rilevata.&#34;
    fi
    info_msg &#34;Fatto.&#34;
}


delete() {
    if get_account &#34;$1&#34;; then
        # Conferma eliminazione
        info_msg &#34;\nAccount:              ${ACCOUNT}\nChiave privata:       ${KEY}\n&#34;
        continue_yn &#34;delete&#34;

        # Cancellazione account
        KEYS_2FA_FILE_DEC_TEMP=$(sed &#34;/$ACCOUNT/d&#34; &lt;&lt;&lt; &#34;${KEYS_2FA_FILE_DEC}&#34;)
        info_msg &#34;L&#39;account \&#34;$ACCOUNT\&#34; è stato eliminato.&#34;

        # Cifratura del nuovo file degli account
        crypt_account_file &#34;${KEYS_2FA_FILE_DEC_TEMP}&#34;
        info_msg &#34;Fatto.&#34;
    else
        err_msg &#34;${ACCOUNT_NOT_FOUND_ERR}&#34; 1
    fi
}


list() {
    echo
    while read LINE; do
        cut -d&#34;${DELIMITER}&#34; -f 1 &lt;&lt;&lt; &#34;${LINE}&#34;
    done &lt;&lt;&lt; &#34;${KEYS_2FA_FILE_DEC}&#34;
}


help() {
cat&lt;&lt;EOF
Usa come: ./totp.sh [options] ARG
dove:
    [options]
        -l restituisce la lista degli account.
        -t &lt;nome_account&gt; restituisce l&#39;otp per quell&#39;account.
        -i &lt;nome_account&gt; inserisce un nuovo account.
        -d &lt;nome_account&gt; cancella &lt;nome_account&gt;.
        -e &lt;nome_account&gt; modifica &lt;nome_account&gt;.
        -h stampa questa pagina di help.

ESEMPI:
    RESTITUISCE LA LISTA DEGLI ACCOUNT
    totp.sh -l

    INSERISCE UN ACCOUNT
    totp.sh -i dropbox

    CANCELLA UN ACCOUNT
    totp.sh -d megaupload

    MODIFICA UN ACCOUNT
    totp.sh -e paypal

    GENERA OTP
    totp.sh -t firefoxsync
EOF
}


main() {
    init
    while getopts &#34;lt:i:e:d:&#34; opt; do
        case $opt in
            l ) list ;;
            t ) totp $OPTARG ;;
            i ) insert $OPTARG ;;
            e ) edit $OPTARG ;;
            d ) delete $OPTARG ;;
            * ) clear;err_msg &#34;${INVALID_OPTION_ERR}&#34; 1;;
        esac
    done
    shift $(($OPTIND - 1))
}

[[ $# -eq 0 || $# -eq 1 &amp;&amp; &#34;$1&#34; == &#34;-h&#34; ]] &amp;&amp; no_param || main $*

</code></pre>

<p><a href="/aytin/tag:bash" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">bash</span></a> <a href="/aytin/tag:totp" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">totp</span></a> <a href="/aytin/tag:cryptography" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">cryptography</span></a> <a href="/aytin/tag:gpg" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">gpg</span></a> <a href="/aytin/tag:scripting" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">scripting</span></a></p>
]]></content:encoded>
      <guid>https://noblogo.org/aytin/gestione-totp-in-bash</guid>
      <pubDate>Wed, 21 Feb 2024 15:37:02 +0000</pubDate>
    </item>
    <item>
      <title>Ancora su brew [upgrade]</title>
      <link>https://noblogo.org/aytin/ancora-su-brew-upgrade</link>
      <description>&lt;![CDATA[(pubblicato il 14 aprile 2021)&#xA;Photo by Mati Mango on Pexels.com&#xA;smalliPhoto by Mati Mango on a href=&#34;https://www.pexels.com/it-it/foto/persona-in-camicia-a-maniche-lunghe-nera-utilizzando-macbook-air-6330644/&#34;Pexels.com/a/i/small&#xA;&#xA;Faccio un po’ d’ordine rispetto all’ultimo post.&#xA;&#xA;brew ha “fattorizzato” la fase di aggiornamento.&#xA;&#xA;Non sono più disponibili le istruzioni esplicite di gestione dei cask perché l’update / upgrade ora comprende formule e cask rendendo il tutto molto più coerente. Quindi:&#xA;!--more--&#xA;&#xA;brew update : Aggiorna il database interno sulle nuove versioni disponibili per formule e cask&#xA;brew upgrade : Aggiorna formule e cask&#xA;brew upgrade --cask : Aggiorna solo i cask&#xA;&#xA;Per quel che riguarda i cask, sappiamo che l’upgrade di brew uprende in considerazione solo i cask che non dispongono della funzionalità di biautoupdate/i/b o che hanno bilatest/i/b come versione./u&#xA;&#xA;Per forzare l’aggiornameno dei cask residui basta una singola istruzione:&#xA;brew upgrade --cask -n --greedy|tail -n +2|grep -v latest|cut -d &#34; &#34; -f 1|xargs brew upgrade --cask&#xA;Aggiorna esplicitamente tutti i cask con autoupdate (esclusi i “latest”)&#xA;&#xA;#brew #bash #macos #scripting]]&gt;</description>
      <content:encoded><![CDATA[<p><strong><em>(pubblicato il 14 aprile 2021)</em></strong>
<img src="https://pixelfed.uno/storage/m/_v2/489827599091373610/42a8ecf32-5a8865/ja1LyaRcn7jS/NM0AI07Y3zsdIZt8bOnBixTprjbfQEbpC5KnqLEQ.jpg" alt="Photo by Mati Mango on Pexels.com">
<small><i>Photo by Mati Mango on <a href="https://www.pexels.com/it-it/foto/persona-in-camicia-a-maniche-lunghe-nera-utilizzando-macbook-air-6330644/" rel="nofollow">Pexels.com</a></i></small></p>

<p>Faccio un po’ d’ordine rispetto <a href="https://noblogo.org/aytin/effettuare-un-brew-upgrade-completo" rel="nofollow">all’ultimo post</a>.</p>

<p><strong>brew</strong> ha “fattorizzato” la fase di aggiornamento.</p>

<p>Non sono più disponibili le istruzioni esplicite di gestione dei cask perché l’update / upgrade ora comprende formule e cask rendendo il tutto molto più coerente. Quindi:
</p>
<ul><li><code>brew update</code> : Aggiorna il database interno sulle nuove versioni disponibili per formule e cask</li>
<li><code>brew upgrade</code> : Aggiorna formule e cask</li>
<li><code>brew upgrade --cask</code> : Aggiorna solo i cask</li></ul>

<p>Per quel che riguarda i cask, sappiamo che l’upgrade di brew <u>prende in considerazione solo i cask che non dispongono della funzionalità di <b><i>autoupdate</i></b> o che hanno <b><i>latest</i></b> come versione.</u></p>

<p>Per forzare l’aggiornameno dei cask residui basta una singola istruzione:</p>

<pre><code class="language-bash">brew upgrade --cask -n --greedy|tail -n +2|grep -v latest|cut -d &#34; &#34; -f 1|xargs brew upgrade --cask
</code></pre>
<ul><li>Aggiorna esplicitamente tutti i cask con autoupdate (esclusi i “latest”)</li></ul>

<p><a href="/aytin/tag:brew" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">brew</span></a> <a href="/aytin/tag:bash" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">bash</span></a> <a href="/aytin/tag:macos" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">macos</span></a> <a href="/aytin/tag:scripting" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">scripting</span></a></p>
]]></content:encoded>
      <guid>https://noblogo.org/aytin/ancora-su-brew-upgrade</guid>
      <pubDate>Wed, 01 Mar 2023 06:17:35 +0000</pubDate>
    </item>
    <item>
      <title>Effettuare un brew upgrade completo</title>
      <link>https://noblogo.org/aytin/effettuare-un-brew-upgrade-completo</link>
      <description>&lt;![CDATA[(pubblicato il 25 marzo 2021)&#xA;laptop&#xA;smalliPhoto by olia danilevich on a href=&#34;https://www.pexels.com/it-it/foto/mani-scrivania-laptop-internet-4974912/&#34;Pexels.com/a/i/small&#xA;&#xA;Sul Mac trovo comodo usare brew . Permette di gestire tante applicazioni con un gestore di pacchetti.&#xA;&#xA;Nota positiva: in generale, funziona bene, per quello che lo conosco.&#xA;Nota negativa: in contraddizione col punto precedente, l’upgrade dei cask mi pare faccia cilecca a id=&#34;linknota1&#34; href=&#34;#nota1&#34; title=&#34;vai alla nota 1&#34;strongsup[1]/sup/strong/a&#xA;&#xA;In passato, eseguivo l’update / upgrade delle formule e l’upgrade dei cask.&#xA;!--more--&#xA;Col tempo, la gestione dei cask è diventata più omogenea con quella delle formule ma l’update / upgrade mi rimane ancora un po’ misterioso.&#xA;&#xA;Eseguendo un brew update &amp;&amp; brew upgrade, soprattutto dalle ultime versioni (dalla 3.0.7 in poi di sicuro), l’update sembra fare il suo lavoro e trova un po’ di cask aggiornare.&#xA;&#xA;Càpita però che l’upgrade ne aggiorni qualcuno, un brew upgrade --cask ne aggiorni qualcun altro (senza raggiungere il totale iniziale) e provando a rifare l’update subito dopo (e anche l’upgrade) il sistema rimane intatto come se fosse up-to-date.&#xA;&#xA;Agendo puntualmente sul cask invece, l’aggiornamento parte eccome. Come se fossero rimaste in cache delle informazioni discordanti sullo stato reale dell’applicazione e su quello potenziale.&#xA;&#xA;Ho cercato un po’ in rete, sembra un problema comune.&#xA;&#xA;Al solito, è partito lo script.&#xA;!/usr/bin/bash&#xA;echo -e &#39;\E[37;44m&#39;&#34;\033[1m**************\033[0m&#34;&#xA;echo -e &#39;\E[37;44m&#39;&#34;\033[1m brew update \033[0m&#34;&#xA;echo -e &#39;\E[37;44m&#39;&#34;\033[1m***********\033[0m&#34;&#xA;brew update&#xA; &#xA; &#xA;echo -e &#34;\n&#34;&#xA;echo -e &#39;\E[37;44m&#39;&#34;\033[1m************\033[0m&#34;&#xA;echo -e &#39;\E[37;44m&#39;&#34;\033[1m brew upgrade \033[0m&#34;&#xA;echo -e &#39;\E[37;44m&#39;&#34;\033[1m************\033[0m&#34;&#xA;if [[ -z $(brew upgrade) ]]; then&#xA;    echo -e &#34;Nothing.&#34;&#xA;fi&#xA; &#xA; &#xA;echo -e &#34;\n&#34;&#xA;echo -e &#39;\E[37;44m&#39;&#34;\033[1m*******************\033[0m&#34;&#xA;echo -e &#39;\E[37;44m&#39;&#34;\033[1m brew upgrade --cask \033[0m&#34;&#xA;echo -e &#39;\E[37;44m&#39;&#34;\033[1m*********************\033[0m&#34;&#xA;for c in $(brew list --cask -1); do&#xA;    printf %-30s &#34;$c&#34;&#xA;    info=$(brew info --cask &#34;$c&#34;)&#xA;     &#xA;    currentver=$(head -n1 &lt;&lt;&lt; &#34;$info&#34; | cut -d &#34; &#34; -f 2)&#xA;    installedver=$(head -n3 &lt;&lt;&lt; &#34;$info&#34; | tail -n1 | cut -d&#34;/&#34; -f 6 | cut -d &#34; &#34; -f 1)&#xA;     &#xA;    if [ &#34;$installedver&#34; != &#34;$currentver&#34; ]; then&#xA;        answer=&#34;&#34;&#xA;        while [[ -z $answer ]]; do&#xA;         &#xA;        read -r -p &#34;$c installed version is &#39;$installedver&#39;, but the current version is &#39;$currentver&#39;. Do you want to upgrade $c? [&#34;$&#39;\e[;32;1mYES\e[0m/no] &#39; answer&#xA; &#xA;        case ${answer,,} in&#xA;            yes|&#34;&#34;)&#xA;                brew upgrade --cask --force $c&#xA;                answer=&#34;yes&#34;&#xA;                ;;&#xA;            no)&#xA;                echo &#34;Installation of $c $currentver skipped&#34;&#xA;                ;;&#xA;            )&#xA;                answer=&#34;&#34;&#xA;                ;;&#xA;            esac&#xA;        done&#xA;    else&#xA;        printf &#34;: \033[38;5;70mis up-to-date (ver. $installedver)\033[0m\n&#34;&#xA;    fi&#xA;done&#xA; &#xA; &#xA;echo -e &#34;\n&#34;&#xA;echo -e &#39;\E[37;44m&#39;&#34;\033[1m***************\033[0m&#34;&#xA;echo -e &#39;\E[37;44m&#39;&#34;\033[1m brew cleanup \033[0m&#34;&#xA;echo -e &#39;\E[37;44m&#39;&#34;\033[1m************\033[0m&#34;&#xA;if [[ -z $(brew cleanup) ]]; then&#xA;    echo -e &#34;Nothing.&#34;&#xA;fi&#xA; &#xA; &#xA;echo -e &#34;\n&#34;&#xA;echo -e &#39;\E[37;44m&#39;&#34;\033[1m***********\033[0m&#34;&#xA;echo -e &#39;\E[37;44m&#39;&#34;\033[1m brew doctor \033[0m&#34;&#xA;echo -e &#39;\E[37;44m&#39;&#34;\033[1m***********\033[0m&#34;&#xA;brew doctor&#xA;Al di là dello zucchero sintattico, la soluzione è stata brutale e ruvida: un ciclo sulla lista dei cask con il force sull’upgrade (righe 21-50). La cosa ancora più singolare è che in questo modo vengono aggiornati dei cask che non risultavano nemmeno nel primo upgrade.&#xA;&#xA;Sicuramente si potrà fare anche meglio. a id=&#34;linknota2&#34; href=&#34;#nota2&#34; title=&#34;vai alla nota 2&#34;strongsup[2]/sup/strong/a&#xA;&#xA;Ho provato a usare brew livecheck per recuperare più elegantemente le info nelle righe 25-26 ma mi dà qualche problema, ogni tanto va in crash. Ho optato per la scansione secca della lista.&#xA;Non so bene come lavori brew outdated, se soffra degli stessi problemi degli altri comandi (sospetto di sì ma devo verificare).&#xA;In realtà basterebbe una sola riga:&#xA;&#xA;brew list --cask|xargs brew upgrade --cask&#xA;solo che così non riesco a controllare il numero di versione causando un aggiornamento quasi sempre inutile per tutti quei cask che hanno come versione “latest” (come Cisco Webex)&#xA;&#xA;Lo script fa il suo lavoro ma ho trovato anche questa alternativa (decisamente più elegante): https://github.com/buo/homebrew-cask-upgrade&#xA;&#xA;Un comando esterno, brew cu nella sua forma compatta, che sostituisce ed estende le funzionalità di brew upgrade. Avendo aggiornato da poco, non l’ho ancora visto all’opera.&#xA;Edit (31/03/2021):&#xA;&#xA;In realtà, leggendo anche il man e il log dell’applicazione su stdout, ho capito che il funzionamento di brew è corretto.&#xA;&#xA;upgrade e outadated prendono in esame solo i cask che non dispongono di autoupdate.&#xA;&#xA;  ==  Casks with ‘autoupdates’ or ‘version :latest’ will not be upgraded; pass \-\-greedy to upgrade them.&#xA;&#xA;Un atteggiamento prudenziale che è facile comprendere. Laddove sia disponibile una funzionalità di autoupdate, si preferisce che sia l’applicazione stessa a gestire questo aspetto, lasciando a brew upgrade --cask (e ai file .rb di configurazione del cask, di conseguenza) la gestione di tutto il resto.&#xA;&#xA;In questo modo, i cask che possono autoaggiornarsi, possono farlo in autonomia appena possibile, senza dipendere dalla modifica necessaria al file di configurazione che innesca l’aggiornamento.&#xA;&#xA;Volendo bypassare questo comportamento, si può ricorrere all’opzione \-\-greedy che forza l’upgrade sia per le app con autoupdate che per quelle che gestiscono il numero di versione con l’orrendo “latest”&#xA;&#xA;Quindi:&#xA;brew outdated --greedy --cask&#xA;e&#xA;brew upgrade --greedy --cask&#xA;permettono rispettivamente:&#xA;&#xA;di elencare tutti i cask che necessitano di un aggiornamento, compresi quelli con autoupdate e con versione “latest”&#xA;di forzare l’aggiornamento dei cask di cui sopra&#xA;&#xA;Forzando l’aggiornamento in questo modo, si potrà andare incontro ad un disallineamento, tipo:&#xA;&#xA;sul sistema può essere presente una versione più aggiornata di quanto non dica il file di configurazione&#xA;affidandosi all’autoupdate, l’opzione \-\-greedy* continuerà a rilevare comunque un aggiornamento da fare (ecco perché brew preferisce ignorare questo tipo di applicazioni)&#xA;&#xA;small&#xA;strongN.B./strong brew cu funziona benissimo permettendo di gestire separatamente sia l’autoupdate che il latest&#xA;/small&#xA;&#xA;ustrongNote all’articolo/strong/usmall&#xA;&#xA;No, non fa cilecca stronga id=&#34;nota1&#34; title=&#34;torna su&#34; href=&#34;#linknota1&#34;[↵]/a/strong&#xA;Ho fatto fuori il ciclo (e non solo) proprio qui stronga id=&#34;nota2&#34; title=&#34;torna su&#34; href=&#34;#linknota2&#34;[↵]/a/strong&#xA;&#xA;/small&#xA;&#xA;#brew #bash #macos #scripting&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p><strong><em>(pubblicato il 25 marzo 2021)</em></strong>
<img src="https://pixelfed.uno/storage/m/_v2/489827599091373610/0fca8ea69-e1c06b/qPu12dRqXK5V/RhP4EHFheAGL3fhsyauMOhKchuWociC9TU3SmuoI.jpg" alt="laptop">
<small><i>Photo by olia danilevich on <a href="https://www.pexels.com/it-it/foto/mani-scrivania-laptop-internet-4974912/" rel="nofollow">Pexels.com</a></i></small></p>

<p>Sul Mac trovo comodo usare <strong>brew</strong> . Permette di gestire tante applicazioni con un gestore di pacchetti.</p>
<ul><li><strong>Nota positiva:</strong> in generale, funziona bene, per quello che lo conosco.</li>
<li><strong>Nota negativa:</strong> in contraddizione col punto precedente, l’upgrade dei cask mi pare faccia cilecca <a id="link_nota_1" href="#nota_1" title="vai alla nota 1" rel="nofollow"><strong><sup>[1]</sup></strong></a></li></ul>

<p>In passato, eseguivo l’update / upgrade delle formule e l’upgrade dei cask.

Col tempo, la gestione dei cask è diventata più omogenea con quella delle formule ma l’update / upgrade mi rimane ancora un po’ misterioso.</p>

<p>Eseguendo un <code>brew update &amp;&amp; brew upgrade</code>, soprattutto dalle ultime versioni (dalla 3.0.7 in poi di sicuro), l’update sembra fare il suo lavoro e trova un po’ di cask aggiornare.</p>

<p>Càpita però che l’upgrade ne aggiorni qualcuno, un <code>brew upgrade --cask</code> ne aggiorni qualcun altro (senza raggiungere il totale iniziale) e provando a rifare l’update subito dopo (e anche l’upgrade) il sistema rimane intatto come se fosse up-to-date.</p>

<p>Agendo puntualmente sul cask invece, l’aggiornamento parte eccome. Come se fossero rimaste in cache delle informazioni discordanti sullo stato reale dell’applicazione e su quello potenziale.</p>

<p>Ho cercato un po’ in rete, sembra un problema comune.</p>

<p>Al solito, è partito lo script.</p>

<pre><code class="language-bash">#!/usr/bin/bash
echo -e &#39;\E[37;44m&#39;&#34;\033[1m***************\033[0m&#34;
echo -e &#39;\E[37;44m&#39;&#34;\033[1m* brew update *\033[0m&#34;
echo -e &#39;\E[37;44m&#39;&#34;\033[1m***************\033[0m&#34;
brew update
 
 
echo -e &#34;\n&#34;
echo -e &#39;\E[37;44m&#39;&#34;\033[1m****************\033[0m&#34;
echo -e &#39;\E[37;44m&#39;&#34;\033[1m* brew upgrade *\033[0m&#34;
echo -e &#39;\E[37;44m&#39;&#34;\033[1m****************\033[0m&#34;
if [[ -z $(brew upgrade) ]]; then
    echo -e &#34;Nothing.&#34;
fi
 
 
echo -e &#34;\n&#34;
echo -e &#39;\E[37;44m&#39;&#34;\033[1m***********************\033[0m&#34;
echo -e &#39;\E[37;44m&#39;&#34;\033[1m* brew upgrade --cask *\033[0m&#34;
echo -e &#39;\E[37;44m&#39;&#34;\033[1m***********************\033[0m&#34;
for c in $(brew list --cask -1); do
    printf %-30s &#34;$c&#34;
    info=$(brew info --cask &#34;$c&#34;)
     
    current_ver=$(head -n1 &lt;&lt;&lt; &#34;$info&#34; | cut -d &#34; &#34; -f 2)
    installed_ver=$(head -n3 &lt;&lt;&lt; &#34;$info&#34; | tail -n1 | cut -d&#34;/&#34; -f 6 | cut -d &#34; &#34; -f 1)
     
    if [ &#34;$installed_ver&#34; != &#34;$current_ver&#34; ]; then
        answer=&#34;&#34;
        while [[ -z $answer ]]; do
         
        read -r -p &#34;$c installed version is &#39;$installed_ver&#39;, but the current version is &#39;$current_ver&#39;. Do you want to upgrade $c? [&#34;$&#39;\e[;32;1mYES\e[0m/no] &#39; answer
 
        case ${answer,,} in
            yes|&#34;&#34;)
                brew upgrade --cask --force $c
                answer=&#34;yes&#34;
                ;;
            no)
                echo &#34;Installation of $c $current_ver skipped&#34;
                ;;
            *)
                answer=&#34;&#34;
                ;;
            esac
        done
    else
        printf &#34;: \033[38;5;70mis up-to-date (ver. $installed_ver)\033[0m\n&#34;
    fi
done
 
 
echo -e &#34;\n&#34;
echo -e &#39;\E[37;44m&#39;&#34;\033[1m****************\033[0m&#34;
echo -e &#39;\E[37;44m&#39;&#34;\033[1m* brew cleanup *\033[0m&#34;
echo -e &#39;\E[37;44m&#39;&#34;\033[1m****************\033[0m&#34;
if [[ -z $(brew cleanup) ]]; then
    echo -e &#34;Nothing.&#34;
fi
 
 
echo -e &#34;\n&#34;
echo -e &#39;\E[37;44m&#39;&#34;\033[1m***************\033[0m&#34;
echo -e &#39;\E[37;44m&#39;&#34;\033[1m* brew doctor *\033[0m&#34;
echo -e &#39;\E[37;44m&#39;&#34;\033[1m***************\033[0m&#34;
brew doctor
</code></pre>

<p>Al di là dello zucchero sintattico, la soluzione è stata brutale e ruvida: un ciclo sulla lista dei cask con il force sull’upgrade (righe 21-50). La cosa ancora più singolare è che in questo modo vengono aggiornati dei cask che non risultavano nemmeno nel primo upgrade.</p>

<p>Sicuramente si potrà fare anche meglio. <a id="link_nota_2" href="#nota_2" title="vai alla nota 2" rel="nofollow"><strong><sup>[2]</sup></strong></a></p>
<ol><li>Ho provato a usare <code>brew livecheck</code> per recuperare più elegantemente le info nelle righe 25-26 ma mi dà qualche problema, ogni tanto va in crash. Ho optato per la scansione secca della lista.</li>
<li>Non so bene come lavori <code>brew outdated</code>, se soffra degli stessi problemi degli altri comandi (sospetto di sì ma devo verificare).</li>
<li>In realtà basterebbe una sola riga:</li></ol>

<pre><code class="language-bash">brew list --cask|xargs brew upgrade --cask
</code></pre>

<p>solo che così non riesco a controllare il numero di versione causando un aggiornamento quasi sempre inutile per tutti quei cask che hanno come versione “latest” (come Cisco Webex)</p>

<p>Lo script fa il suo lavoro ma ho trovato anche questa alternativa (decisamente più elegante): <a href="https://github.com/buo/homebrew-cask-upgrade" rel="nofollow">https://github.com/buo/homebrew-cask-upgrade</a></p>

<p>Un comando esterno, <code>brew cu</code> nella sua forma compatta, che sostituisce ed estende le funzionalità di <code>brew upgrade</code>. Avendo aggiornato da poco, non l’ho ancora visto all’opera.</p>

<h2 id="edit-31-03-2021">Edit (31/03/2021):</h2>

<p>In realtà, leggendo anche il man e il log dell’applicazione su stdout, ho capito che il funzionamento di brew è corretto.</p>

<p><em>upgrade</em> e <em>outadated</em> prendono in esame solo i cask che <strong>non dispongono di autoupdate</strong>.</p>

<blockquote><p>==&gt; Casks with ‘auto_updates’ or ‘version :latest’ will not be upgraded; pass --greedy to upgrade them.</p></blockquote>

<p>Un atteggiamento prudenziale che è facile comprendere. Laddove sia disponibile una funzionalità di autoupdate, si preferisce che <strong>sia l’applicazione stessa a gestire questo aspetto</strong>, lasciando a <code>brew upgrade --cask</code> (e ai file .rb di configurazione del cask, di conseguenza) la gestione di tutto il resto.</p>

<p>In questo modo, i cask che possono autoaggiornarsi, possono farlo in autonomia appena possibile, senza dipendere dalla modifica necessaria al file di configurazione che innesca l’aggiornamento.</p>

<p>Volendo bypassare questo comportamento, si può ricorrere all’opzione <em>--greedy</em> che forza l’upgrade sia per le app con autoupdate che per quelle che gestiscono il numero di versione con l’orrendo “latest”</p>

<p>Quindi:</p>

<pre><code class="language-bash">brew outdated --greedy --cask
</code></pre>

<p>e</p>

<pre><code class="language-bash">brew upgrade --greedy --cask
</code></pre>

<p>permettono rispettivamente:</p>
<ul><li>di elencare <strong>tutti</strong> i cask che necessitano di un aggiornamento, compresi quelli con autoupdate e con versione “latest”</li>
<li>di forzare l’aggiornamento dei cask di cui sopra</li></ul>

<p>Forzando l’aggiornamento in questo modo, si potrà andare incontro ad un disallineamento, tipo:</p>
<ul><li>sul sistema può essere presente una versione più aggiornata di quanto non dica il file di configurazione</li>
<li>affidandosi all’autoupdate, l’opzione <em>--greedy</em> continuerà a rilevare comunque un aggiornamento da fare (ecco perché brew preferisce ignorare questo tipo di applicazioni)</li></ul>

<p><small>
<strong>N.B.</strong> <code>brew cu</code> funziona benissimo permettendo di gestire separatamente sia l’autoupdate che il latest
</small></p>

<p><u><strong>Note all’articolo</strong></u><small></p>
<ol><li>No, non fa cilecca <strong><a id="nota_1" title="torna su" href="#link_nota_1" rel="nofollow">[↵]</a></strong></li>
<li>Ho fatto fuori il ciclo (e non solo) proprio qui <strong><a id="nota_2" title="torna su" href="#link_nota_2" rel="nofollow">[↵]</a></strong></li></ol>

<p></small></p>

<p><a href="/aytin/tag:brew" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">brew</span></a> <a href="/aytin/tag:bash" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">bash</span></a> <a href="/aytin/tag:macos" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">macos</span></a> <a href="/aytin/tag:scripting" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">scripting</span></a></p>
]]></content:encoded>
      <guid>https://noblogo.org/aytin/effettuare-un-brew-upgrade-completo</guid>
      <pubDate>Tue, 28 Feb 2023 22:16:57 +0000</pubDate>
    </item>
    <item>
      <title>Reload Radio Deejay in bash</title>
      <link>https://noblogo.org/aytin/reload-radio-deejay-in-bash</link>
      <description>&lt;![CDATA[(pubblicato il 22 marzo 2021)&#xA;vecchia radio&#xA;small/iPhoto by Nothing Ahead on a href=&#34;https://www.pexels.com/it-it/foto/shallow-focus-foto-di-una-vecchia-radio-3199028/&#34;Pexels.com/a/i/small&#xA;&#xA;Questo script permette di scaricare le puntate intere di Ciao Belli o DeeJay Time da Deejay Reload perché le ascolto offline praticamente da sempre. In alternativa, è possibile ascoltare direttamente il live streaming delle trasmissioni.&#xA;!--more--&#xA;I pattern che uso per lo scraping sono molto elementari e sono ovviamente sensibili ai minimi cambiamenti che avvengono sulle pagine di Radio Deejay.&#xA;&#xA;La versione attuale è attiva da un paio di mesi a questa parte perché il pattern prima era leggermente diverso. E più si va indietro, più le differenze aumentano. Per dirne una, lo streaming hls è molto recente, Prima di esso, il reload era disponibile solo in mp3. Questa sarà la 15supa/sup versione dello script 🙂&#xA;&#xA;Il download restituisce un file in mp3 o aac. Quest&#39;ultimo ottenuto in maniera rudimentale estrando i singoli segmenti aac ottenuti dall&#39;analisi della playlist hls e ricombinati attraverso ffmpeg.&#xA;&#xA;In linea di principio sarebbe possibile estendere lo script aumentando la scelta delle trasmissioni disponibili e, volendo migliorare l&#39;interazione con l&#39;utente sacrificando l&#39;automatismo e la &#34;scriptabilità&#34;, dotandolo di un menù.&#xA;&#xA;help() {&#xA;    echo &#34;$1&#34;&#xA;    echo&#xA;    echo &#34;Usa come: wgetdeejay.sh YYYYMMDD prg format&#34;&#xA;    echo &#34;Es. wgetdeejay.sh 2019062 cb mp3  [Scarica Ciao Belli in mp3]&#34;&#xA;    echo &#34;    wgetdeejay.sh 2019062 djt aac [Scarica Deejay Time in aac]&#34;&#xA;    echo &#34;    wgetdeejay.sh live            [Deejay Live]&#34;&#xA;}&#xA;&#xA;download() {&#xA;    if [[ ${FMT} == &#34;mp3&#34; || -z ${FMT} ]]; then&#xA;        if ! checkanddownloadmp3; then&#xA;            return 1&#xA;        fi&#xA;    elif [[ ${FMT} == &#34;aac&#34; ]]; then&#xA;        if ! checkaac; then&#xA;            return 1&#xA;        else&#xA;            downloadaac&#xA;        fi&#xA;    else&#xA;        help &#34;Formato non corretto.&#34;&#xA;        return 1&#xA;    fi&#xA;}&#xA;&#xA;checkanddownloadmp3() {&#xA;    #if ! wget -q https://${HOST}/${DATAURL}/episodes/${TITLE}/${DATA}.mp3 -O &#34;${PRG}${DATA}.mp3&#34;; then&#xA;    echo https://${HOST}/${DATAURL}/episodes/${TITLE}/${TITLE}-${DATA}.mp3&#xA;    if ! wget https://${HOST}/${DATAURL}/episodes/${TITLE}/${TITLE}-${DATA}.mp3 -O &#34;${PRG}${DATA}.mp3&#34;; then&#xA;        help &#34;Puntata di ${TITLE} del ${ERRDATA}, non trovata.&#34;&#xA;        return 1&#xA;    fi&#xA;}&#xA;&#xA;checkaac() {&#xA;    #if ! wget -qO- https://${HOST}/&#34;${DATAURL}&#34;/episodes/${TITLE}/hls-010000/hls-010000.m3u8 1  /dev/null; then&#xA;    echo https://${HOST}/&#34;${DATAURL}&#34;/episodes/${TITLE}/hls-&#34;${TITLE}&#34;-&#34;${DATA}&#34;/hls-&#34;${TITLE}&#34;-&#34;${DATA}&#34;.m3u8&#xA;    if ! wget -qO- https://${HOST}/&#34;${DATAURL}&#34;/episodes/${TITLE}/hls-&#34;${TITLE}&#34;-&#34;${DATA}&#34;/hls-&#34;${TITLE}&#34;-&#34;${DATA}&#34;.m3u8 1  /dev/null; then&#xA;        help &#34;Puntata di ${TITLE} del ${ERRDATA}, non trovata.&#34;&#xA;        return 1&#xA;    else&#xA;        return 0&#xA;    fi&#xA;}&#xA;&#xA;downloadaac() {&#xA;    echo -n &#34;Scarico la traccia............. &#34;&#xA;    ffmpeg -i https://${HOST}/&#34;${DATAURL}&#34;/episodes/${TITLE}/hls-&#34;${TITLE}&#34;-&#34;${DATA}&#34;/hls-&#34;${TITLE}&#34;-&#34;${DATA}&#34;.m3u8 -c copy $HOME/deejay/&#34;${PRG}${DATA}&#34;.aac 2  /dev/null&#xA;    echo &#34;Fatto.&#34;&#xA;&#xA;}&#xA;&#xA;DATA=$1&#xA;PRG=$2&#xA;FMT=$3&#xA;&#xA;if [[ $1 == &#34;live&#34; ]]; then&#xA;    mpv http://radiodeejay-lh.akamaihd.net/i/RadioDeejayLive1@189857/master.m3u8&#xA;else&#xA;    case &#34;${PRG}&#34; in&#xA;        &#34;cb&#34;)&#xA;            HOST=&#34;media.deejay.it&#34;&#xA;            TITLE=&#34;ciaobelli&#34;&#xA;            ;;&#xA;        &#34;djt&#34;)&#xA;            HOST=&#34;media.deejay.it&#34;&#xA;            TITLE=&#34;deejaytime&#34;&#xA;&#xA;            ;;&#xA;        &#34;live&#34;)&#xA;            TITLE=&#34;live&#34;&#xA;            ;;&#xA;        *)&#xA;            help &#34;Programma Deejay mancante o non esistente.&#34;&#xA;            exit 1&#xA;    esac&#xA;&#xA;    DATAURL=&#34;${DATA:0:4}/${DATA:4:2}/${DATA:6:2}&#34;&#xA;    ERR_DATA=&#34;${DATA:6:2}-${DATA:4:2}-${DATA:0:4}&#34;&#xA;&#xA;    if [[ -z ${DATA:0:4} ]]; then help &#34;Manca l&#39;anno.&#34;; exit 1&#xA;    elif [[ -z ${DATA:4:2} ]]; then help &#34;Manca il mese.&#34;; exit 1&#xA;    elif [[ -z ${DATA:6:2} ]]; then help &#34;Manca il giorno.&#34;; exit 1&#xA;    elif ! download; then exit 1&#xA;    fi&#xA;fi&#xA;&#xA;#bash #shell #scripting]]&gt;</description>
      <content:encoded><![CDATA[<p><strong><em>(pubblicato il 22 marzo 2021)</em></strong>
<img src="https://pixelfed.uno/storage/m/_v2/489827599091373610/0fca8ea69-e1c06b/gx5yE0mToMHs/pgHaEJhxeXnpa868gj4wz1feQ3PMi6077g1Q1eFd.jpg" alt="vecchia radio">
<small></i>Photo by Nothing Ahead on <a href="https://www.pexels.com/it-it/foto/shallow-focus-foto-di-una-vecchia-radio-3199028/" rel="nofollow">Pexels.com</a></i></small></p>

<p>Questo script permette di scaricare le puntate intere di <strong>Ciao Belli</strong> o <strong>DeeJay Time</strong> da Deejay Reload perché le ascolto offline praticamente da sempre. In alternativa, è possibile ascoltare direttamente il live streaming delle trasmissioni.

I pattern che uso per lo scraping sono molto elementari e sono ovviamente sensibili ai minimi cambiamenti che avvengono sulle pagine di <a href="https://www.deejay.it" rel="nofollow">Radio Deejay</a>.</p>

<p>La versione attuale è attiva da un paio di mesi a questa parte perché il pattern prima era leggermente diverso. E più si va indietro, più le differenze aumentano. Per dirne una, lo <em>streaming hls</em> è molto recente, Prima di esso, il reload era disponibile solo in mp3. Questa sarà la 15<sup>a</sup> versione dello script 🙂</p>

<p>Il download restituisce un file in mp3 o aac. Quest&#39;ultimo ottenuto in maniera rudimentale estrando i singoli segmenti aac ottenuti dall&#39;analisi della playlist hls e ricombinati attraverso ffmpeg.</p>

<p>In linea di principio sarebbe possibile estendere lo script aumentando la scelta delle trasmissioni disponibili e, volendo migliorare l&#39;interazione con l&#39;utente sacrificando l&#39;automatismo e la “scriptabilità”, dotandolo di un menù.</p>

<pre><code class="language-bash">help() {
    echo &#34;$1&#34;
    echo
    echo &#34;Usa come: wget_deejay.sh &lt;YYYYMMDD&gt; &lt;prg&gt; &lt;format&gt;&#34;
    echo &#34;Es. wget_deejay.sh 2019062 cb mp3  [Scarica Ciao Belli in mp3]&#34;
    echo &#34;    wget_deejay.sh 2019062 djt aac [Scarica Deejay Time in aac]&#34;
    echo &#34;    wget_deejay.sh live            [Deejay Live]&#34;
}



download() {
    if [[ ${FMT} == &#34;mp3&#34; || -z ${FMT} ]]; then
        if ! check_and_download_mp3; then
            return 1
        fi
    elif [[ ${FMT} == &#34;aac&#34; ]]; then
        if ! check_aac; then
            return 1
        else
            download_aac
        fi
    else
        help &#34;Formato non corretto.&#34;
        return 1
    fi
}



check_and_download_mp3() {
    #if ! wget -q https://${HOST}/${DATA_URL}/episodes/${TITLE}/${DATA}.mp3 -O &#34;${PRG}_${DATA}.mp3&#34;; then
    echo https://${HOST}/${DATA_URL}/episodes/${TITLE}/${TITLE}-${DATA}.mp3
    if ! wget https://${HOST}/${DATA_URL}/episodes/${TITLE}/${TITLE}-${DATA}.mp3 -O &#34;${PRG}_${DATA}.mp3&#34;; then
        help &#34;Puntata di ${TITLE} del ${ERR_DATA}, non trovata.&#34;
        return 1
    fi
}



check_aac() {
    #if ! wget -qO- https://${HOST}/&#34;${DATA_URL}&#34;/episodes/${TITLE}/hls-010000/hls-010000.m3u8 1&gt;/dev/null; then
    echo https://${HOST}/&#34;${DATA_URL}&#34;/episodes/${TITLE}/hls-&#34;${TITLE}&#34;-&#34;${DATA}&#34;/hls-&#34;${TITLE}&#34;-&#34;${DATA}&#34;.m3u8
    if ! wget -qO- https://${HOST}/&#34;${DATA_URL}&#34;/episodes/${TITLE}/hls-&#34;${TITLE}&#34;-&#34;${DATA}&#34;/hls-&#34;${TITLE}&#34;-&#34;${DATA}&#34;.m3u8 1&gt;/dev/null; then
        help &#34;Puntata di ${TITLE} del ${ERR_DATA}, non trovata.&#34;
        return 1
    else
        return 0
    fi
}


download_aac() {
    echo -n &#34;Scarico la traccia............. &#34;
    ffmpeg -i https://${HOST}/&#34;${DATA_URL}&#34;/episodes/${TITLE}/hls-&#34;${TITLE}&#34;-&#34;${DATA}&#34;/hls-&#34;${TITLE}&#34;-&#34;${DATA}&#34;.m3u8 -c copy $HOME/deejay/&#34;${PRG}_${DATA}&#34;.aac 2&gt;/dev/null
    echo &#34;Fatto.&#34;

}


DATA=$1
PRG=$2
FMT=$3


if [[ $1 == &#34;live&#34; ]]; then
    mpv http://radiodeejay-lh.akamaihd.net/i/RadioDeejay_Live_1@189857/master.m3u8
else
    case &#34;${PRG}&#34; in
        &#34;cb&#34;)
            HOST=&#34;media.deejay.it&#34;
            TITLE=&#34;ciao_belli&#34;
            ;;
        &#34;djt&#34;)
            HOST=&#34;media.deejay.it&#34;
            TITLE=&#34;deejay_time&#34;

            ;;
        &#34;live&#34;)
            TITLE=&#34;live&#34;
            ;;
        *)
            help &#34;Programma Deejay mancante o non esistente.&#34;
            exit 1
    esac


    DATA_URL=&#34;${DATA:0:4}/${DATA:4:2}/${DATA:6:2}&#34;
    ERR_DATA=&#34;${DATA:6:2}-${DATA:4:2}-${DATA:0:4}&#34;

    if [[ -z ${DATA:0:4} ]]; then help &#34;Manca l&#39;anno.&#34;; exit 1
    elif [[ -z ${DATA:4:2} ]]; then help &#34;Manca il mese.&#34;; exit 1
    elif [[ -z ${DATA:6:2} ]]; then help &#34;Manca il giorno.&#34;; exit 1
    elif ! download; then exit 1
    fi
fi
</code></pre>

<p><a href="/aytin/tag:bash" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">bash</span></a> <a href="/aytin/tag:shell" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">shell</span></a> <a href="/aytin/tag:scripting" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">scripting</span></a></p>
]]></content:encoded>
      <guid>https://noblogo.org/aytin/reload-radio-deejay-in-bash</guid>
      <pubDate>Tue, 28 Feb 2023 20:16:39 +0000</pubDate>
    </item>
    <item>
      <title>Generazione OTP via bash</title>
      <link>https://noblogo.org/aytin/generazione-otp-via-bash</link>
      <description>&lt;![CDATA[(pubblicato il 30 gennaio 2021)&#xA;otp&#xA;smalliFonte: a href=&#34;https://www.flaticon.com/free-icons/otp&#34; title=&#34;otp icons&#34;Otp icons created by Chanut-is-Industries - Flaticon/a/i/small&#xA;&#xA;Per buona parte del mio tempo sul pc apro una shell, per cui trovo comodo spostare il meno possibile le mani dalla tastiera. Usando spesso 2FA, ho pensato ad un modo per avere un OTP usando bash.&#xA;!--more--&#xA;Il tool che fa al caso mio è oathtool che, per i miei scopi, è sufficiente invocare in questo modo:&#xA;oathtool --totp -b key&#xA;dove \key\ è una stringa base32 bella lunga.&#xA;&#xA;Chiaramente non voglio doverla scrivere ogni volta, nè voglio lasciarla in chiaro. Occorre che:&#xA;&#xA;la chiave sia cifrata e decifrata solamente quando occorre;&#xA;l&#39;output non arrivi su stdout per non doverlo copiare a mia volta (e lasciarlo comunque visibile)&#xA;&#xA;Al punto 1. si risponde con gpg o openssl.&#xA;&#xA;Al punto 2. si risponde con xsel (Gnu/Linux) o pbcopy (MacOS) che trasferiscono l&#39;output di un comando nella clipboard.&#xA;oathtool --totp -b &lt;&lt; $(gpg -q --decrypt --pinentry-mode loopback &lt;filechiavecifrato) | xsel -bi&#xA;E veniamo quindi alla:&#xA;Versione 1&#xA;!/bin/bash&#xA;&#xA;# Path del file cifrato contentente la chiave # supponiamo sia di google&#xA;KEY2FAFILE=&#34;$1&#34;&#xA;&#xA;oathtool --totp -b &lt;&lt;&lt; $(gpg -q --decrypt --pinentry-mode loopback &#34;${KEY2FAFILE}&#34;) | xsel -bi&#xA;Non è nemmeno uno script. Potrebbe essere direttamente un alias.&#xA;&#xA;ustrongVANTAGGI/strong/u&#xA;&#xA;Estremamente compatto&#xA;Si sfrutta nativamente l&#39;autocomplete col TAB per i file da dare all&#39;input&#xA;L&#39;univocità dei nomi è garantita dal file system&#xA;&#xA;ustrongSVANTAGGI/strong/u&#xA;&#xA;Crea un file per ogni codice e, alla lunga potrebbe generare confusione&#xA;Necessità di creare una password di cifratura per ogni file (potenzialmente tutte diverse)&#xA;&#xA;Versione 2&#xA;Mi occorre quindi un file nome-valore con separatore. Ad es. il simbolo  &#34;:&#34;&#xA;account1:JD88EIDIKJDKMDI3IEJDMDKJKDJKDKPA&#xA;account2:JDNBLASPUQRIEKM89478JMFKOVJDOQKJ&#xA;account3:OIJWGDVLOIQ94KDKSUD9KSLWOER9W3ODF&#xA;account4:MVNMWIQPQYSKGPRIGNFG24KFG9E49RPWQ&#xA;    &#xA;    ...&#xA;    &#xA;accountn:IWPQSLNCGK49TOWODIR483IRIWOFOPIQO&#xA;Nello script, la variabile KEYS\2FA\FILE memorizza il full path del file cifrato dei codici, mentre l&#39;account deve essere fornito in input. In caso contrario, viene restituita la lista degli account disponibili.&#xA;&#xA; Se l&#39;account è valido, il file viene decifrato e viene estratto il corrispondente codice che viene memorizzato direttamente nella clipboard.&#xA;!/usr/bin/bash&#xA;&#xA;Path del file cifrato contentente i codici 2FA&#xA;KEYS2FAFILE=&#34;path/keys2fa.gpg&#34;&#xA;&#xA;Acquisisce il nome account&#xA;ACCOUNT=$1&#xA;&#xA;Decifra e carica in ram il contenuto del file cifrato&#xA;KEYS2FAFILEDEC=$(gpg --decrypt  --pinentry-mode loopback  &#34;${KEYS2FAFILE}&#34; 2  /dev/null)&#xA;&#xA;Se non c&#39;è nessun argomento, restituisce la lista degli account&#xA;if [[ -z &#34;${ACCOUNT}&#34; ]]; then&#xA;    while read LINE; do&#xA;        cut -d&#34;:&#34; -f 1 &lt;&lt;&lt; ${LINE}&#xA;    done &lt;&lt;&lt; $(echo &#34;${KEYS2FAFILEDEC}&#34;)&#xA;    exit&#xA;else&#xA;    # Estrae l&#39;elemento contenente l&#39;id dato in input&#xA;    KEY=$(echo &#34;${KEYS2FAFILEDEC}&#34; | grep ${ACCOUNT} | cut -d&#34;:&#34; -f 2)&#xA;&#xA;    # Se la chiave esiste, restituisce l&#39;otp direttamente nella clipboard&#xA;    # altrimenti esce con errore&#xA;    if [[ -z &#34;${KEY}&#34; ]]; then&#xA;        echo &#34;Account non esistente&#34;&#xA;        exit 1&#xA;    else&#xA;        # Calcola l&#39;otp e lo trasferisce nella clipboard con pbcopy&#xA;        oathtool --totp -b &#34;${KEY}&#34; | xsel -bi&#xA;        echo &#34;Fatto.&#34;&#xA;    fi&#xA;fi&#xA;ustrongVANTAGGI/strong/u&#xA;&#xA;Unico entry-point per la gestione degli account (un&#39;unica password per la cifratura del file&#xA;Più semplice da allocare&#xA;&#xA;ustrongSVANTAGGI/strong/u&#xA;&#xA;L&#39;univocità degli account deve essere garantita dall&#39;utente.&#xA;Perdita della funzionalità di autocomplete in fase di inserimento dati&#xA;&#xA;Provo ad esplorare anche una struttura differente, più espressiva se vogliamo. Invece di un file nome-valore, uso un json così strutturato:&#xA;&#xA;Versione 3&#xA;{&#xA;  &#34;accounts&#34;: [&#xA;    {&#xA;      &#34;id&#34;: &#34;account1&#34;,&#xA;      &#34;secret&#34;: &#34;JD88EIDIKJDKMDI3IEJDMDKJKDJKDKPA&#34;&#xA;    },&#xA;    {&#xA;      &#34;id&#34;: &#34;account2&#34;,&#xA;      &#34;secret&#34;: &#34;JDNBLASPUQRIEKM89478JMFKOVJDOQKJ&#34;&#xA;    },&#xA;    {&#xA;      &#34;id&#34;: &#34;account3&#34;,&#xA;      &#34;secret&#34;: &#34;OIJWGDVLOIQ94KDKSUD9KSLWOER9W3ODF&#34;&#xA;    },&#xA;    {&#xA;      &#34;id&#34;: &#34;account4&#34;,&#xA;      &#34;secret&#34;: &#34;MVNMWIQPQYSKGPRIGNFG24KFG9E49RPWQ&#34;&#xA;    },&#xA;    &#xA;    ...&#xA;    &#xA;    {&#xA;      &#34;id&#34;: &#34;accountn&#34;,&#xA;      &#34;secret&#34;: &#34;IWPQSLNCGK49TOWODIR483IRIWOFOPIQO&#34;&#xA;    }&#xA;  ]&#xA;}&#xA;Un array, accounts, di oggetti nome-valore.&#xA;&#xA;Lo script che fa il parsing e l&#39;estrazione non è molto diverso dal precedente.&#xA;!/usr/local/bin/bash&#xA;&#xA;Path del file cifrato contentente i codici 2FA&#xA;JSON2FAFILE=path/json2fa.gpg&#xA;&#xA;Acquisisce il nome account&#xA;ACCOUNT=$1&#xA;&#xA;Carica in ram il contenuto del file json cifrato&#xA;JSON2FAFILEDEC=$(gpg --decrypt  --pinentry-mode loopback  &#34;${JSON2FAFILE}&#34; 2  /dev/null)&#xA;&#xA;Se non c&#39;è nessun argomento, restituisce la lista degli account&#xA;- jq -c &#39;.accounts[]&#39; restituisce una lista contenenente tante linee per quanti sono gli elementi.&#xA;- La lista viene data in pasto al ciclo while attraverso l&#39;operatore &lt;&lt;&lt; (here-string)&#xA;- Da ogni linea viene estratto il valore del campo &#34;id&#34; sempre attraverso l&#39;operatore &lt;&lt;&lt;&#xA;&#xA;if [[ -z &#34;${ACCOUNT}&#34; ]]; then&#xA;    while read LINE; do&#xA;        jq -r &#39;.id&#39; &lt;&lt;&lt; ${LINE}&#xA;    done &lt;&lt;&lt; $(echo &#34;${JSON2FAFILEDEC}&#34; | jq -c &#39;.accounts[]&#39;)&#xA;    exit&#xA;else&#xA;    # Estrae dall&#39;array l&#39;elemento contenente l&#39;id dato in input&#xA;    KEY=$(jq -r --arg ID ${ACCOUNT} &#39;.accounts[] | select(.id | contains($ID)).secret&#39; &lt;&lt;&lt; ${JSON2FAFILE_DEC})&#xA;&#xA;    # Se la chiave esiste, restituisce l&#39;otp direttamente nella clipboard&#xA;    # altrimenti esce con errore&#xA;    if [[ -z &#34;${KEY}&#34; ]]; then&#xA;        echo &#34;Account non esistente&#34;&#xA;        exit 1&#xA;    else&#xA;        # Calcola l&#39;otp e lo trasferisce nella clipboard con xsel&#xA;        oathtool --totp -b &#34;${KEY}&#34; | xsel -bi&#xA;        echo &#34;Fatto.&#34;&#xA;    fi&#xA;fi&#xA;#bash #otp #scripting #shell ]]&gt;</description>
      <content:encoded><![CDATA[<p><strong><em>(pubblicato il 30 gennaio 2021)</em></strong>
<img src="https://pixelfed.uno/storage/m/_v2/489827599091373610/0fca8ea69-e1c06b/Jm77gCXX9v4y/7IVttwKSmhThLBGuFctXh2BMZau1cgRi2Grgjhmh.jpg" alt="otp">
<small><i>Fonte: <a href="https://www.flaticon.com/free-icons/otp" title="otp icons" rel="nofollow">Otp icons created by Chanut-is-Industries – Flaticon</a></i></small></p>

<p>Per buona parte del mio tempo sul pc <a href="https://noblogo.org/aytin/merge-delle-history-in-tempo-reale" rel="nofollow">apro una shell</a>, per cui trovo comodo spostare il meno possibile le mani dalla tastiera. Usando spesso 2FA, ho pensato ad un modo per avere un OTP usando bash.

Il tool che fa al caso mio è <a href="http://www.nongnu.org/oath-toolkit/oathtool.1.html" rel="nofollow">oathtool</a> che, per i miei scopi, è sufficiente invocare in questo modo:</p>

<pre><code class="language-bash">oathtool --totp -b &lt;key&gt;
</code></pre>

<p>dove <em>&lt;key&gt;</em> è una stringa base32 bella lunga.</p>

<p>Chiaramente non voglio doverla scrivere ogni volta, nè voglio lasciarla in chiaro. Occorre che:</p>
<ol><li>la chiave sia cifrata e decifrata solamente quando occorre;</li>
<li>l&#39;output non arrivi su stdout per non doverlo copiare a mia volta (e lasciarlo comunque visibile)</li></ol>

<p>Al punto 1. si risponde con <a href="https://www.gnupg.org" rel="nofollow">gpg</a> o <a href="https://www.openssl.org" rel="nofollow">openssl</a>.</p>

<p>Al punto 2. si risponde con <strong>xsel</strong> (Gnu/Linux) o <strong>pbcopy</strong> (MacOS) che trasferiscono l&#39;output di un comando nella clipboard.</p>

<pre><code class="language-bash">oathtool --totp -b &lt;&lt;&lt; $(gpg -q --decrypt --pinentry-mode loopback &lt;file_chiave_cifrato&gt;) | xsel -bi
</code></pre>

<p>E veniamo quindi alla:</p>

<h2 id="versione-1">Versione 1</h2>

<pre><code class="language-bash">#!/bin/bash

# Path del file cifrato contentente la chiave # supponiamo sia di google
KEY_2FA_FILE=&#34;$1&#34;

oathtool --totp -b &lt;&lt;&lt; $(gpg -q --decrypt --pinentry-mode loopback &#34;${KEY_2FA_FILE}&#34;) | xsel -bi
</code></pre>

<p>Non è nemmeno uno script. Potrebbe essere direttamente un alias.</p>

<p><u><strong>VANTAGGI</strong></u></p>
<ul><li>Estremamente compatto</li>
<li>Si sfrutta nativamente l&#39;autocomplete col TAB per i file da dare all&#39;input</li>
<li>L&#39;univocità dei nomi è garantita dal file system</li></ul>

<p><u><strong>SVANTAGGI</strong></u></p>
<ul><li>Crea un file per ogni codice e, alla lunga potrebbe generare confusione</li>
<li>Necessità di creare una password di cifratura per ogni file (potenzialmente tutte diverse)</li></ul>

<h2 id="versione-2">Versione 2</h2>

<p>Mi occorre quindi un file nome-valore con separatore. Ad es. il simbolo  “:”</p>

<pre><code>account_1:JD88EIDIKJDKMDI3IEJDMDKJKDJKDKPA
account_2:JDNBLASPUQRIEKM89478JMFKOVJDOQKJ
account_3:OIJWGDVLOIQ94KDKSUD9KSLWOER9W3ODF
account_4:MVNMWIQPQYSKGPRIGNFG24KFG9E49RPWQ
    
    ...
    
account_n:IWPQSLNCGK49TOWODIR483IRIWOFOPIQO
</code></pre>

<p>Nello script, la variabile <strong>KEYS_2FA_FILE</strong> memorizza il full path del file cifrato dei codici, mentre l&#39;account deve essere fornito in input. In caso contrario, viene restituita la lista degli account disponibili.</p>

<p> Se l&#39;account è valido, il file viene decifrato e viene estratto il corrispondente codice che viene memorizzato direttamente nella clipboard.</p>

<pre><code class="language-bash">#!/usr/bin/bash

# Path del file cifrato contentente i codici 2FA
KEYS_2FA_FILE=&#34;&lt;path&gt;/keys_2fa.gpg&#34;

# Acquisisce il nome account
ACCOUNT=$1

# Decifra e carica in ram il contenuto del file cifrato
KEYS_2FA_FILE_DEC=$(gpg --decrypt  --pinentry-mode loopback  &#34;${KEYS_2FA_FILE}&#34; 2&gt;/dev/null)

# Se non c&#39;è nessun argomento, restituisce la lista degli account
if [[ -z &#34;${ACCOUNT}&#34; ]]; then
    while read LINE; do
        cut -d&#34;:&#34; -f 1 &lt;&lt;&lt; ${LINE}
    done &lt;&lt;&lt; $(echo &#34;${KEYS_2FA_FILE_DEC}&#34;)
    exit
else
    # Estrae l&#39;elemento contenente l&#39;id dato in input
    KEY=$(echo &#34;${KEYS_2FA_FILE_DEC}&#34; | grep ${ACCOUNT} | cut -d&#34;:&#34; -f 2)

    # Se la chiave esiste, restituisce l&#39;otp direttamente nella clipboard
    # altrimenti esce con errore
    if [[ -z &#34;${KEY}&#34; ]]; then
        echo &#34;Account non esistente&#34;
        exit 1
    else
        # Calcola l&#39;otp e lo trasferisce nella clipboard con pbcopy
        oathtool --totp -b &#34;${KEY}&#34; | xsel -bi
        echo &#34;Fatto.&#34;
    fi
fi
</code></pre>

<p><u><strong>VANTAGGI</strong></u></p>
<ul><li>Unico entry-point per la gestione degli account (un&#39;unica password per la cifratura del file</li>
<li>Più semplice da allocare</li></ul>

<p><u><strong>SVANTAGGI</strong></u></p>
<ul><li>L&#39;univocità degli account deve essere garantita dall&#39;utente.</li>
<li>Perdita della funzionalità di autocomplete in fase di inserimento dati</li></ul>

<p>Provo ad esplorare anche una struttura differente, più espressiva se vogliamo. Invece di un file nome-valore, uso un json così strutturato:</p>

<h2 id="versione-3">Versione 3</h2>

<pre><code>{
  &#34;accounts&#34;: [
    {
      &#34;id&#34;: &#34;account_1&#34;,
      &#34;secret&#34;: &#34;JD88EIDIKJDKMDI3IEJDMDKJKDJKDKPA&#34;
    },
    {
      &#34;id&#34;: &#34;account_2&#34;,
      &#34;secret&#34;: &#34;JDNBLASPUQRIEKM89478JMFKOVJDOQKJ&#34;
    },
    {
      &#34;id&#34;: &#34;account_3&#34;,
      &#34;secret&#34;: &#34;OIJWGDVLOIQ94KDKSUD9KSLWOER9W3ODF&#34;
    },
    {
      &#34;id&#34;: &#34;account_4&#34;,
      &#34;secret&#34;: &#34;MVNMWIQPQYSKGPRIGNFG24KFG9E49RPWQ&#34;
    },
    
    ...
    
    {
      &#34;id&#34;: &#34;account_n&#34;,
      &#34;secret&#34;: &#34;IWPQSLNCGK49TOWODIR483IRIWOFOPIQO&#34;
    }
  ]
}
</code></pre>

<p>Un array, <em>accounts</em>, di oggetti nome-valore.</p>

<p>Lo script che fa il parsing e l&#39;estrazione non è molto diverso dal precedente.</p>

<pre><code class="language-bash">#!/usr/local/bin/bash

# Path del file cifrato contentente i codici 2FA
JSON_2FA_FILE=&lt;path&gt;/json_2fa.gpg

# Acquisisce il nome account
ACCOUNT=$1

# Carica in ram il contenuto del file json cifrato
JSON_2FA_FILE_DEC=$(gpg --decrypt  --pinentry-mode loopback  &#34;${JSON_2FA_FILE}&#34; 2&gt;/dev/null)

# Se non c&#39;è nessun argomento, restituisce la lista degli account
# - jq -c &#39;.accounts[]&#39; restituisce una lista contenenente tante linee per quanti sono gli elementi.
# - La lista viene data in pasto al ciclo while attraverso l&#39;operatore &lt;&lt;&lt; (here-string)
# - Da ogni linea viene estratto il valore del campo &#34;id&#34; sempre attraverso l&#39;operatore &lt;&lt;&lt;

if [[ -z &#34;${ACCOUNT}&#34; ]]; then
    while read LINE; do
        jq -r &#39;.id&#39; &lt;&lt;&lt; ${LINE}
    done &lt;&lt;&lt; $(echo &#34;${JSON_2FA_FILE_DEC}&#34; | jq -c &#39;.accounts[]&#39;)
    exit
else
    # Estrae dall&#39;array l&#39;elemento contenente l&#39;id dato in input
    KEY=$(jq -r --arg ID ${ACCOUNT} &#39;.accounts[] | select(.id | contains($ID)).secret&#39; &lt;&lt;&lt; ${JSON_2FA_FILE_DEC})

    # Se la chiave esiste, restituisce l&#39;otp direttamente nella clipboard
    # altrimenti esce con errore
    if [[ -z &#34;${KEY}&#34; ]]; then
        echo &#34;Account non esistente&#34;
        exit 1
    else
        # Calcola l&#39;otp e lo trasferisce nella clipboard con xsel
        oathtool --totp -b &#34;${KEY}&#34; | xsel -bi
        echo &#34;Fatto.&#34;
    fi
fi
</code></pre>

<p><a href="/aytin/tag:bash" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">bash</span></a> <a href="/aytin/tag:otp" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">otp</span></a> <a href="/aytin/tag:scripting" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">scripting</span></a> <a href="/aytin/tag:shell" class="hashtag" rel="nofollow"><span>#</span><span class="p-category">shell</span></a></p>
]]></content:encoded>
      <guid>https://noblogo.org/aytin/generazione-otp-via-bash</guid>
      <pubDate>Tue, 28 Feb 2023 07:59:24 +0000</pubDate>
    </item>
  </channel>
</rss>