#!/bin/bash # # PREPARA IL PC: accesso Gitea, accesso AWS, git, chiave SSH, nomi e chiavi dei server. # # ./configura.sh # # Si lancia nel proprio terminale: chiede la password Gitea e la chiave AWS # con l'input nascosto, e un assistente AI non puo' rispondere al posto tuo. # Si puo' rilanciare: quello che e' gia' a posto non viene toccato. # Non crea accessi: quelli li crea un amministratore, qui si usano. # Esce 0 se e' andato tutto, 1 se qualcosa resta da sistemare (detto in rosso). set -u cd "$(dirname "$0")" || exit 1 . ./comune.sh # Un passo non riuscito non ferma gli altri (la chiave SSH serve anche senza # AWS), ma si conta: alla fine "Fatto" si dice solo se non ce ne sono, e # l'uscita e' 1, cosi' chi lo lancia da uno script non va avanti alla cieca. ERRORI=0 errore () { male "$@" ; ERRORI=$((ERRORI + 1)) ; } # -------------------------------------------------------------------------- titolo "1. Gitea" # -------------------------------------------------------------------------- # Alla persona si chiede la password, non un token: il token lo crea lo # script con l'API di Gitea, cosi' non c'e' da cercarlo fra le impostazioni. # La password e' nella scheda LastPass "Gitea > nome.cognome", condivisa da # chi amministra (nella mail non c'e', apposta): dal 07/10/2026 Gitea non # obbliga piu' a cambiarla. Chi e' stato creato prima invece deve ancora # cambiarla nel browser, e Gitea gli risponde 403: lo si dice sotto. UTENTE="$(utente_gitea)" ; ESITO_GITEA=$? # Gitea che non risponde non vuol dire token sbagliato: chiedere la password e # creare un altro token non servirebbe a niente. Ci si ferma qui, perche' tutto # il resto (chi e' la persona, dove va la chiave SSH) lo dice Gitea. if [ "$ESITO_GITEA" = "2" ] ; then male "Gitea ($GITEA_URL) non risponde: controlla la connessione a internet e rilancia." exit 1 fi # I token creati prima del 07/10/2026 hanno read:organization e non # write:organization, che serve a creare i progetti nell'organizzazione oto. # Lo si chiede a Gitea con una creazione volutamente incompleta (senza nome): # a un token senza lo scope risponde 403 citandolo, a uno giusto 422, e in # nessuno dei due casi crea niente. Il token vecchio si mette da parte e se ne # crea uno nuovo qui sotto; quello vecchio si cancella dal proprio profilo. if [ -n "$UTENTE" ] && api_gitea /orgs/oto/repos -X POST -H 'Content-Type: application/json' -d '{}' \ | grep -q 'write:organization' ; then mv "$TOKEN_GITEA_FILE" "$TOKEN_GITEA_FILE.vecchio" attento "il token Gitea salvato non puo' creare progetti nell'organizzazione: ne creo uno nuovo" echo " (quello vecchio, in $TOKEN_GITEA_FILE.vecchio, cancellalo dal tuo profilo Gitea:" echo " Impostazioni -> Applicazioni)" UTENTE="" fi if [ -n "$UTENTE" ] ; then ok "token Gitea gia' salvato (utente $UTENTE)" else echo " L'utente Gitea e' nella mail \"I tuoi accessi OTO\" che ti ha mandato chi" echo " amministra. La password no: e' su LastPass, nella scheda \"Gitea > nome.cognome\"" echo " col tuo utente (per esempio \"Gitea > sara.locatelli\"), campo Password." read -r -p " Il tuo utente Gitea (dalla mail, riga utente Gitea): " UTENTE_DATO read -r -s -p " La tua password Gitea, dalla scheda LastPass \"Gitea > $UTENTE_DATO\" (non si vede, ed e' normale): " PASSWORD ; echo # utente:password passano a curl da stdin, non dalla riga di comando # (che chiunque sul PC vede con ps). Virgolette e \ vanno protette. PASSWORD_CURL="$(printf '%s:%s' "$UTENTE_DATO" "$PASSWORD" | sed 's/[\\"]/\\&/g')" NOME_TOKEN="oto-$(hostname | tr -dc 'A-Za-z0-9-' | head -c 20)-$(date +%Y%m%d%H%M%S)" RISPOSTA="$(printf 'user = "%s"\n' "$PASSWORD_CURL" \ | curl -s --max-time 15 -K - -w '\n%{http_code}' -X POST \ -H 'Content-Type: application/json' \ -d "{\"name\":\"$NOME_TOKEN\",\"scopes\":[\"write:repository\",\"write:user\",\"write:organization\"]}" \ "$GITEA_URL/api/v1/users/$UTENTE_DATO/tokens")" unset PASSWORD PASSWORD_CURL CODICE="${RISPOSTA##*$'\n'}" TOKEN="$(printf '%s' "${RISPOSTA%$'\n'*}" | campo_json sha1)" if [ "$CODICE" = "201" ] && [ -n "$TOKEN" ] ; then mkdir -p "$(dirname "$TOKEN_GITEA_FILE")" chmod 700 "$(dirname "$TOKEN_GITEA_FILE")" ( umask 077 ; printf '%s\n' "$TOKEN" > "$TOKEN_GITEA_FILE" ) chmod 600 "$TOKEN_GITEA_FILE" unset TOKEN UTENTE="$(utente_gitea)" if [ -n "$UTENTE" ] ; then ok "accesso Gitea salvato in $TOKEN_GITEA_FILE (utente $UTENTE)" else errore "Gitea ha creato l'accesso ma non lo riconosce: rilancia, e se si ripete avvisa l'amministratore." fi elif [ "$CODICE" = "403" ] ; then errore "Gitea chiede prima di cambiare la password: entra su $GITEA_URL (Sign in), scegline una nuova, salvala su LastPass e rilancia." elif [ "$CODICE" = "401" ] ; then errore "utente o password sbagliati: rilancia e riprova." else errore "Gitea non ha creato l'accesso (HTTP ${CODICE:-nessuna risposta}): rilancia, o chiedi all'amministratore." fi fi # git: chi sei, e come entra su Gitea. Il token vale anche come password per # git, e lo tiene il gestore "store" (il file ~/.git-credentials) per il solo # indirizzo di Gitea: un gestore gia' configurato per tutto il resto (quello di # Windows o del Mac, per dire) non si tocca, ma per Gitea non viene interrogato, # cosi' non risponde con un accesso vecchio o con una finestra di login. if [ -n "$UTENTE" ] ; then [ -n "$(git config --global user.name)" ] || git config --global user.name "$UTENTE" if [ -z "$(git config --global user.email)" ] ; then read -r -p " La tua email di lavoro, quella @oto.agency (firma le tue modifiche al codice): " EMAIL [ -n "$EMAIL" ] && git config --global user.email "$EMAIL" fi # La riga vuota azzera, per questo indirizzo, i gestori configurati prima. git config --global --unset-all "credential.$GITEA_URL.helper" 2> /dev/null git config --global --add "credential.$GITEA_URL.helper" "" git config --global --add "credential.$GITEA_URL.helper" store # Se git ha ancora il token di prima (rinnovato qui sopra, o revocato), la # credenziale si sostituisce: prima si aggiungeva solo se mancava, e restava # quella vecchia, con l'API che funzionava e git clone che no. Passa da git, # su stdin, e tocca la sola voce di Gitea. if [ "$(password_git_gitea)" != "$(token_gitea)" ] ; then printf 'protocol=https\nhost=%s\n\n' "$GITEA_HOST" | git credential reject printf 'protocol=https\nhost=%s\nusername=%s\npassword=%s\n\n' "$GITEA_HOST" "$UTENTE" "$(token_gitea)" \ | git credential approve fi [ -f "$HOME/.git-credentials" ] && chmod 600 "$HOME/.git-credentials" if [ "$(password_git_gitea)" = "$(token_gitea)" ] ; then ok "git configurato ($(git config --global user.name) <$(git config --global user.email)>)" else errore "git non riesce a ricordare l'accesso a Gitea: avvisa l'amministratore, con il messaggio qui sopra." fi fi # -------------------------------------------------------------------------- titolo "2. AWS" # -------------------------------------------------------------------------- # La chiave di accesso la crea chi amministra insieme all'utente, e la # persona la trova su LastPass, nel campo di testo della scheda # "AWS > Nome Cognome": qui la si incolla # una volta e finisce in ~/.aws/credentials, dove la leggono la AWS CLI e gli # script dei progetti. Se non ce l'ha (e' arrivata prima, o l'ha persa), se # ne crea una dalla console: la strada e' scritta qui sotto. # # Viene dopo Gitea apposta: e' Gitea, con la password, a dire chi e' la # persona. Una chiave gia' presente sul PC non basta a dirlo: puo' essere # rimasta da chi lo usava prima, o copiata da un collega, e allora tutto # quello che la persona fa su AWS risulterebbe fatto da un altro. Per questo # senza Gitea il passo si salta, invece di dare per buona una chiave qualsiasi. # # La chiave si scrive nel profilo "default" e si controlla con quello: se # AWS_PROFILE o le variabili AWS_ACCESS_KEY_ID... sono impostate, la AWS CLI # userebbe altro, e lo si dice. AWS_OPZ=(--profile default --cli-connect-timeout 10 --cli-read-timeout 15) identita_aws () { AWS_MAX_ATTEMPTS=1 aws sts get-caller-identity "${AWS_OPZ[@]}" --query Arn --output text 2> /dev/null } chiedi_chiave_aws () { echo " Serve la tua chiave di accesso AWS, due pezzi: Access key ID e Secret access key." # Il nome della scheda e' quello per esteso: sara.locatelli -> Sara Locatelli. SCHEDA_AWS="AWS > $(printf '%s' "$UTENTE" | awk -F. '{for (i = 1; i <= NF; i++) printf "%s%s", (i > 1 ? " " : ""), toupper(substr($i, 1, 1)) substr($i, 2)}')" echo " Non sono nella mail: li trovi su LastPass, nella scheda \"$SCHEDA_AWS\" che ti" echo " ha condiviso chi amministra, nel campo di testo (righe Access key ID e Secret" echo " access key). Se li' non c'e', la crei tu nella console AWS:" echo " 1. entra su $AWS_CONSOLE (IAM user, utente, password e" echo " codice a due fattori dalla stessa scheda LastPass);" echo " 2. il tuo nome in alto a destra → Security credentials;" echo " 3. riquadro Access keys → Create access key → Command Line Interface (CLI)," echo " spunta la conferma → Next → Create access key." echo " I due pezzi sono nella pagina che si apre (Retrieve access keys): la Secret" echo " si vede cliccando Show, e una volta sola. Se hai chiuso quella pagina," echo " cancella la chiave dallo stesso riquadro e creane un'altra." echo " Non si vede mentre la incolli, ed e' normale." read -r -s -p " Access key ID (inizia con AKIA): " CHIAVE_ID ; echo read -r -s -p " Secret access key: " CHIAVE_SEGRETA ; echo CHIAVE_ID="$(printf '%s' "$CHIAVE_ID" | tr -d '[:space:]')" CHIAVE_SEGRETA="$(printf '%s' "$CHIAVE_SEGRETA" | tr -d '[:space:]')" if [ -z "$CHIAVE_ID" ] || [ -z "$CHIAVE_SEGRETA" ] ; then errore "chiave non inserita: rilancia quando ce l'hai." ; unset CHIAVE_SEGRETA ; return 1 fi # Si prova PRIMA di salvarla, passandola per variabili d'ambiente (che non # si vedono con ps): una chiave sbagliata, o di un altro, non deve prendere # il posto di quella che c'era. local nuova nuova="$(env -u AWS_PROFILE -u AWS_SESSION_TOKEN -u AWS_SHARED_CREDENTIALS_FILE \ AWS_ACCESS_KEY_ID="$CHIAVE_ID" AWS_SECRET_ACCESS_KEY="$CHIAVE_SEGRETA" AWS_MAX_ATTEMPTS=1 \ aws sts get-caller-identity --cli-connect-timeout 10 --cli-read-timeout 15 --query Arn --output text 2> /dev/null)" if [ -z "$nuova" ] ; then unset CHIAVE_SEGRETA errore "AWS non accetta questa chiave: controlla di aver copiato i due pezzi interi, senza scambiarli, e rilancia. Non l'ho salvata." return 1 fi if ! aws_e_di "$nuova" "$UTENTE" ; then unset CHIAVE_SEGRETA errore "questa chiave e' di ${nuova#arn:aws:iam::}, non tua ($UTENTE nell'account OTO $AWS_ACCOUNT): usa quella della tua scheda LastPass \"$SCHEDA_AWS\", e rilancia. Non l'ho salvata." return 1 fi # aws configure set la vuole come argomento: per l'attimo in cui gira si # vede con ps. Non c'e' un'alternativa da stdin, e su un PC personale e' # un rischio accettato. aws configure set aws_access_key_id "$CHIAVE_ID" --profile default \ && aws configure set aws_secret_access_key "$CHIAVE_SEGRETA" --profile default \ && aws configure set region "$AWS_REGIONE" --profile default local esito=$? unset CHIAVE_SEGRETA [ -f "$HOME/.aws/credentials" ] && chmod 600 "$HOME/.aws/credentials" if [ "$esito" != "0" ] ; then errore "non riesco a salvare la chiave in ~/.aws/credentials: avvisa l'amministratore, con il messaggio qui sopra." return 1 fi ok "accesso AWS: $UTENTE" } if ! c_e aws ; then errore "AWS CLI non installata: installala e rilancia questo script." elif [ -z "$UTENTE" ] ; then errore "AWS saltato: prima deve funzionare Gitea (passo 1), che dice chi sei. Sistemato quello, rilancia." else CONFLITTI="$(aws_variabili_in_conflitto)" if [ -n "$CONFLITTI" ] ; then attento "nel terminale sono impostate $CONFLITTI: la AWS CLI usera' quelle e non la chiave salvata qui. Se non sai perche' ci sono, dillo all'amministratore." fi if IDENTITA="$(identita_aws)" && [ -n "$IDENTITA" ] ; then if aws_e_di "$IDENTITA" "$UTENTE" ; then ok "accesso AWS: $UTENTE" else attento "su questo PC c'e' gia' una chiave AWS, ma e' di ${IDENTITA#arn:aws:iam::}, non tua ($UTENTE)." echo " Usandola, tutto quello che fai su AWS risulterebbe fatto da un altro." echo " La sostituisco con la tua. Avvisa l'amministratore: quella chiave e' uscita" echo " dal PC del suo proprietario e va disattivata." chiedi_chiave_aws fi else chiedi_chiave_aws fi fi # -------------------------------------------------------------------------- titolo "3. Chiave SSH" # -------------------------------------------------------------------------- if [ -f "$CHIAVE_SSH" ] ; then ok "chiave gia' presente: $CHIAVE_SSH" else mkdir -p "$HOME/.ssh" && chmod 700 "$HOME/.ssh" COMMENTO="${UTENTE:-$(whoami)}@oto" if ssh-keygen -q -t ed25519 -f "$CHIAVE_SSH" -C "$COMMENTO" -N "" ; then ok "chiave creata: $CHIAVE_SSH" else errore "non riesco a creare la chiave SSH: avvisa l'amministratore, con il messaggio qui sopra." fi fi # Gli script dei progetti la cercano con altri nomi (staging e produzione): # sono collegamenti a questa, non chiavi nuove. Un file che c'e' gia' con # quel nome e' una chiave tua di prima, e non si tocca. if [ -f "$CHIAVE_SSH" ] ; then for ALTRA in $CHIAVI_SSH_PROGETTI ; do [ -e "$ALTRA" ] && continue ln -s "$(basename "$CHIAVE_SSH")" "$ALTRA" 2> /dev/null || cp -p "$CHIAVE_SSH" "$ALTRA" \ || errore "non riesco a creare $ALTRA: i deploy dei progetti non troverebbero la chiave." done fi # La parte pubblica va sul proprio profilo Gitea: e' da li' che # l'amministratore la prende per metterla sui server, senza che la persona # debba mandarla a nessuno. if [ -f "$CHIAVE_SSH.pub" ] && [ -n "$UTENTE" ] ; then PUBBLICA="$(awk '{print $1" "$2}' "$CHIAVE_SSH.pub")" CHIAVI="$(api_gitea /user/keys)" if [ "${CHIAVI##*$'\n'}" = "200" ] && printf '%s' "$CHIAVI" | grep -qF "${PUBBLICA#* }" ; then ok "chiave pubblica gia' sul tuo profilo Gitea" else CODICE="$(api_gitea /user/keys -o /dev/null -X POST -H 'Content-Type: application/json' \ -d "{\"title\":\"oto $(hostname | tr -dc 'A-Za-z0-9-')\",\"key\":\"$PUBBLICA\"}")" CODICE="${CODICE##*$'\n'}" if [ "$CODICE" = "201" ] ; then ok "chiave pubblica caricata sul tuo profilo Gitea: l'amministratore la prende da li'" else errore "non riesco a caricare la chiave su Gitea (HTTP $CODICE). Manda all'amministratore questa riga, e' la parte pubblica:" echo " $(cat "$CHIAVE_SSH.pub")" fi fi echo " Il file $CHIAVE_SSH, senza .pub, non esce mai da questo PC." fi # -------------------------------------------------------------------------- titolo "4. Nomi dei server in ~/.ssh/config" # -------------------------------------------------------------------------- # Un blocco fra due segni, riscritto intero a ogni giro: cosi' quando cambia # un server basta rilanciare. Un nome gia' definito fuori dal blocco non si # tocca (e' una configurazione tua). CONFIG="$HOME/.ssh/config" mkdir -p "$HOME/.ssh" && chmod 700 "$HOME/.ssh" touch "$CONFIG" && chmod 600 "$CONFIG" INIZIO="# >>> OTO (scritto da _OTO_IT_onboard/configura.sh) >>>" FINE_BLOCCO="# <<< OTO <<<" # Confronto esatto, non una regex: il segno contiene "/" e "(". FUORI="$(awk -v i="$INIZIO" -v f="$FINE_BLOCCO" '$0 == i {dentro=1; next} $0 == f {dentro=0; next} !dentro' "$CONFIG")" # La chiave con ~ e non col percorso intero: su Windows Git Bash scriverebbe # /c/Users/..., che l'OpenSSH di Windows (quello di VS Code) non capisce. CHIAVE_CONFIG="~${CHIAVE_SSH#"$HOME"}" BLOCCO="$INIZIO" for VOCE in "$STAGING_NOME $STAGING_HOST $STAGING_UTENTE" \ "$PRODUZIONE_NOME $PRODUZIONE_HOST $PRODUZIONE_UTENTE" \ "$PRODUZIONE_VECCHIA_NOME $PRODUZIONE_VECCHIA_HOST $PRODUZIONE_VECCHIA_UTENTE" ; do set -- $VOCE if printf '%s\n' "$FUORI" | grep -qE "^Host[[:space:]]+$1([[:space:]]|\$)" ; then attento "$1 e' gia' definito in $CONFIG: lo lascio com'e'" continue fi BLOCCO="$BLOCCO Host $1 HostName $2 User $3 IdentitiesOnly yes IdentityFile $CHIAVE_CONFIG " done BLOCCO="$BLOCCO$FINE_BLOCCO" if { printf '%s\n' "$FUORI" | sed -e :a -e '/^\n*$/{$d;N;ba' -e '}' ; echo ; printf '%s\n' "$BLOCCO" ; } > "$CONFIG.nuovo" \ && mv "$CONFIG.nuovo" "$CONFIG" && chmod 600 "$CONFIG" ; then ok "ssh $STAGING_NOME, ssh $PRODUZIONE_NOME e ssh $PRODUZIONE_VECCHIA_NOME: configurati" else rm -f "$CONFIG.nuovo" errore "non riesco a scrivere $CONFIG: i nomi dei server non sono configurati." fi # -------------------------------------------------------------------------- titolo "5. Chiavi dei server in ~/.ssh/known_hosts" # -------------------------------------------------------------------------- # Quelle di server.known_hosts, lette sulle macchine: cosi' il primo # collegamento non chiede "Are you sure you want to continue connecting?", # e la risposta non dipende da chi e' in mezzo alla rete quel giorno. Quelle # che c'erano per gli stessi nomi si tolgono prima (ssh-keygen ne lascia una # copia in known_hosts.old): una vecchia rimasta li' farebbe dire a ssh che # il server e' cambiato. Quando un server cambia chiave davvero, la nuova si # legge sulla macchina e si scrive in server.known_hosts, in questo # repository: e' l'unico posto da cui la prendono i PC. NOTI="$HOME/.ssh/known_hosts" touch "$NOTI" && chmod 600 "$NOTI" for H in $(grep -v '^#' server.known_hosts | awk 'NF {print $1}' | sort -u) ; do ssh-keygen -R "$H" -f "$NOTI" > /dev/null 2>&1 done if grep -v '^#' server.known_hosts | awk 'NF' >> "$NOTI" ; then ok "chiavi di $STAGING_NOME, $PRODUZIONE_NOME e $PRODUZIONE_VECCHIA_NOME registrate" else errore "non riesco a scrivere $NOTI: ssh chiedera' conferma al primo collegamento." fi # -------------------------------------------------------------------------- titolo "Fatto" # -------------------------------------------------------------------------- if [ "$ERRORI" != "0" ] ; then if [ "$ERRORI" = "1" ] ; then male "una cosa non e' andata (sopra, in rosso): sistemala e rilancia ./configura.sh." else male "$ERRORI cose non sono andate (sopra, in rosso): sistemale e rilancia ./configura.sh." ; fi exit 1 fi echo " Ora: se su LastPass hai la scheda \"OTO > VPN HK $UTENTE\", accendi la VPN (file e credenziali sono li'); poi torna da Claude."