onboard: niente piu' esiti verdi su passi falliti, git e AWS verificati davvero

- configura.sh e progetti.sh escono 1 se un passo non riesce (scrittura di
  ~/.ssh/config, chiave SSH, clone, chiave AWS), e lo dicono in rosso;
- git: per l'indirizzo di Gitea si usa solo il gestore "store", e un token
  vecchio rimasto in ~/.git-credentials si sostituisce (prima si aggiungeva
  solo se mancava: API buona e git clone rotto). verifica.sh chiede a git la
  credenziale che userebbe davvero, invece di accontentarsi di un gestore
  qualsiasi;
- AWS: la chiave si controlla PRIMA di salvarla, e deve essere l'utente della
  persona nell'account di OTO (ARN completo, non solo il nome). Senza Gitea il
  passo si salta e verifica.sh non da' piu' verde a una chiave qualsiasi.
  Avviso se AWS_PROFILE o AWS_ACCESS_KEY_ID... fanno usare un'altra chiave;
- API di Gitea: codice HTTP controllato, "Gitea non risponde" distinto da
  "token non valido", elenco dei progetti interrotto solo su una pagina vuota
  vera; token passato a curl da stdin; JSON letto anche con gli spazi;
- progetti.sh: nomi confrontati in modo esatto, accetta organizzazione/nome
  e dice quando un nome corto e' ambiguo;
- la chiave ~/.ssh/oto_ed25519 viene collegata anche ai nomi che cercano i
  deploy dei progetti (oto_staging_ed25519, oto_production_ed25519): su un
  PC preparato da qui il primo deploy si fermava con "Chiave privata non
  trovata";
- verifica.sh --senza-vpn, per chi non ha la VPN: staging giallo e non rosso.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Fabio
2026-10-11 10:27:17 +02:00
co-authored by Claude Opus 5.5
parent e544e90661
commit eb68d8930e
5 changed files with 326 additions and 105 deletions
+11 -5
View File
@@ -81,8 +81,8 @@ mail, le servono tutti i passi; se su LastPass ha anche la scheda `OTO > VPN HK
serve anche la VPN. Chi lavora solo dal pannello del sito non riceve la mail
e non arriva qui. Alla VPN (passo 5) le chiedi soltanto se fra le schede
LastPass condivise con lei c'è `OTO > VPN HK nome.cognome`: se no, salti il
passo, e `verifica.sh` segnerà rossi VPN e staging: dille che per lei è
normale.
passo, e lanci `./verifica.sh --senza-vpn`: staging non raggiungibile
diventa giallo invece che rosso, e il risultato può essere tutto a posto.
Niente gergo: se una parola tecnica serve (terminale, token, push),
spiegala in una riga la prima volta.
@@ -120,7 +120,11 @@ spiegala in una riga la prima volta.
Gitea, configura git, genera la chiave SSH e la carica sul suo profilo
Gitea (da lì la prende l'amministratore: **non va mandata a nessuno**),
scrive i nomi dei server in `~/.ssh/config` e le loro chiavi in
`~/.ssh/known_hosts` (da `server.known_hosts`). Gitea viene prima apposta: è lui a dire chi è la persona. Se sul PC
`~/.ssh/known_hosts` (da `server.known_hosts`). La chiave la crea come
`~/.ssh/oto_ed25519` e la collega anche ai due nomi con cui la cercano i
deploy dei progetti, `oto_staging_ed25519` e `oto_production_ed25519`
(un file che c'è già con quel nome non lo tocca). Esce con errore se un
passo non è riuscito, e lo dice in rosso: allora non si va avanti. Gitea viene prima apposta: è lui a dire chi è la persona. Se sul PC
c'è già una chiave AWS **di un altro** (rimasta da chi lo usava, o copiata
da un collega), lo script lo dice e chiede la sua: dille di avvisare
l'amministratore, perché quella chiave va disattivata. Una chiave altrui
@@ -138,8 +142,10 @@ spiegala in una riga la prima volta.
scrivendo «ho finito la configurazione». È il segnale
per mettere la sua chiave SSH sui server; finché non lo fa, staging resta
rosso.
7. **`./verifica.sh`**: tutto verde, o un elenco preciso di cosa manca e a chi
chiederlo.
7. **`./verifica.sh`** (`--senza-vpn` se non ha la VPN): tutto verde, o un
elenco preciso di cosa manca e a chi chiederlo. Controlla anche che git
entri su Gitea con il token giusto e che la chiave AWS sia la sua
nell'account di OTO, non solo un utente con lo stesso nome.
8. **`./progetti.sh`**: scarica i progetti che la persona vede su Gitea (lo
lanci tu con i nomi: `./progetti.sh nome1 nome2`). Poi, in ogni progetto:
`./start.sh` per i progetti con pannello, `bin/local-init.sh` per Drupal e
+61 -7
View File
@@ -6,12 +6,17 @@
GITEA_URL="https://git.oto-farm.com"
GITEA_HOST="git.oto-farm.com"
AWS_REGIONE="eu-central-1"
AWS_CONSOLE="https://170187898933.signin.aws.amazon.com/console"
AWS_ACCOUNT="170187898933"
AWS_CONSOLE="https://$AWS_ACCOUNT.signin.aws.amazon.com/console"
# Dove stanno le cose della persona, sul suo PC. Gli script dei progetti
# leggono da qui: non cambiarli senza cambiare anche loro.
TOKEN_GITEA_FILE="$HOME/.config/oto/gitea.token"
CHIAVE_SSH="$HOME/.ssh/oto_ed25519"
# I nomi con cui la cercano gli script dei progetti (STAGING_SSH_KEY e
# PRODUCTION_SSH_KEY in project.conf.sh): sono la stessa chiave, perche' sui
# server l'amministratore mette quella del profilo Gitea, cioe' questa.
CHIAVI_SSH_PROGETTI="$HOME/.ssh/oto_staging_ed25519 $HOME/.ssh/oto_production_ed25519"
CARTELLA_PROGETTI="${CARTELLA_PROGETTI:-$HOME/Work/workspace/oto}"
# I server, con i nomi che usano gli script dei progetti. Staging e la
@@ -50,11 +55,60 @@ token_gitea () {
[ -s "$TOKEN_GITEA_FILE" ] && tr -d '[:space:]' < "$TOKEN_GITEA_FILE"
}
# Stampa il nome utente Gitea del token, o niente se il token non vale.
# Una chiamata all'API di Gitea col token salvato: il token passa a curl da
# stdin, non dalla riga di comando (che sul PC si vede con ps). Scrive il corpo
# della risposta e, sull'ultima riga, il codice HTTP (000 se Gitea non ha
# risposto). Gli argomenti in piu' vanno a curl (-X POST, -d ...).
api_gitea () {
local percorso="$1" ; shift
printf 'header = "Authorization: token %s"\n' "$(token_gitea)" \
| curl -s --max-time 15 -K - -w '\n%{http_code}' "$@" "$GITEA_URL/api/v1$percorso"
}
# Il valore di un campo testo da un JSON di Gitea, anche con gli spazi dopo i
# due punti. Basta per i campi che servono qui; un parser vero vorrebbe jq, che
# su Windows non c'e'.
campo_json () {
sed -n "s/.*\"$1\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" | head -n 1
}
# Stampa il nome utente Gitea del token. Esce 1 se il token manca o Gitea lo
# rifiuta, 2 se Gitea non risponde: "non vale" e "non so" sono due cose
# diverse, e la seconda non deve far chiedere di nuovo la password.
utente_gitea () {
local token
token="$(token_gitea)" || return 1
[ -n "$token" ] || return 1
curl -s --max-time 10 -H "Authorization: token $token" "$GITEA_URL/api/v1/user" \
| sed -n 's/.*"login":"\([^"]*\)".*/\1/p'
local risposta codice
[ -n "$(token_gitea)" ] || return 1
risposta="$(api_gitea /user)"
codice="${risposta##*$'\n'}"
case "$codice" in
200) printf '%s' "${risposta%$'\n'*}" | campo_json login ;;
401|403) return 1 ;;
*) return 2 ;;
esac
}
# Controlla che l'identita' AWS (un ARN) sia l'utente IAM della persona
# nell'account di OTO, e non solo un utente con lo stesso nome altrove.
aws_e_di () {
[ "$1" = "arn:aws:iam::$AWS_ACCOUNT:user/$2" ]
}
# Le variabili che fanno usare alla AWS CLI un'altra chiave invece di quella
# scritta in ~/.aws/credentials: le stampa, senza i valori.
aws_variabili_in_conflitto () {
local v
for v in AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_PROFILE AWS_SHARED_CREDENTIALS_FILE ; do
[ -n "${!v:-}" ] && printf '%s ' "$v"
done
}
# La credenziale che git usa davvero per Gitea, chiesta a git e non letta da
# un file: e' quella che serve per clone e pull, qualunque gestore la tenga.
# Senza nessuna domanda: nel terminale di VS Code GIT_ASKPASS aprirebbe una
# finestra di login.
password_git_gitea () {
printf 'protocol=https\nhost=%s\n\n' "$GITEA_HOST" \
| env -u GIT_ASKPASS -u SSH_ASKPASS GIT_TERMINAL_PROMPT=0 GCM_INTERACTIVE=never \
git -c credential.interactive=never credential fill 2> /dev/null \
| sed -n 's/^password=//p'
}
+147 -48
View File
@@ -8,11 +8,18 @@
# 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"
# --------------------------------------------------------------------------
@@ -22,15 +29,21 @@ titolo "1. Gitea"
# 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)"
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" ] && curl -s --max-time 10 -X POST -H "Authorization: token $(token_gitea)" \
-H 'Content-Type: application/json' -d '{}' "$GITEA_URL/api/v1/orgs/oto/repos" \
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"
@@ -57,37 +70,58 @@ else
"$GITEA_URL/api/v1/users/$UTENTE_DATO/tokens")"
unset PASSWORD PASSWORD_CURL
CODICE="${RISPOSTA##*$'\n'}"
TOKEN="$(printf '%s' "${RISPOSTA%$'\n'*}" | sed -n 's/.*"sha1":"\([^"]*\)".*/\1/p')"
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)"
elif [ "$CODICE" = "403" ] ; then
male "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
male "utente o password sbagliati: rilancia e riprova."
else
male "Gitea non ha creato l'accesso (HTTP ${CODICE:-nessuna risposta}): rilancia, o chiedi all'amministratore."
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 si ricorda le credenziali. Il token vale anche come
# password per git: lo si scrive una volta in ~/.git-credentials.
# 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
[ -n "$(git config --global credential.helper)" ] || git config --global credential.helper store
CREDENZIALI="$HOME/.git-credentials"
if ! grep -q "@$GITEA_HOST" "$CREDENZIALI" 2> /dev/null ; then
( umask 077 ; printf 'https://%s:%s@%s\n' "$UTENTE" "$(token_gitea)" "$GITEA_HOST" >> "$CREDENZIALI" )
# 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
# --------------------------------------------------------------------------
@@ -103,11 +137,21 @@ titolo "2. AWS"
# 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.
# 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:-nome.cognome}" | awk -F. '{for (i = 1; i <= NF; i++) printf "%s%s", (i > 1 ? " " : ""), toupper(substr($i, 1, 1)) substr($i, 2)}')"
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:"
@@ -125,36 +169,64 @@ chiedi_chiave_aws () {
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
male "chiave non inserita: rilancia quando ce l'hai." ; unset CHIAVE_SEGRETA ; return 1
errore "chiave non inserita: rilancia quando ce l'hai." ; unset CHIAVE_SEGRETA ; return 1
fi
aws configure set aws_access_key_id "$CHIAVE_ID"
aws configure set aws_secret_access_key "$CHIAVE_SEGRETA"
aws configure set region "$AWS_REGIONE"
# 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
if ! IDENTITA="$(aws sts get-caller-identity --query Arn --output text 2> /dev/null)" ; then
male "AWS non accetta questa chiave: controlla di aver copiato i due pezzi interi, senza scambiarli, e rilancia."
elif [ -n "$UTENTE" ] && [ "${IDENTITA##*/}" != "$UTENTE" ] ; then
male "questa chiave e' di ${IDENTITA##*/}, non tua ($UTENTE): usa quella della tua scheda LastPass \"$SCHEDA_AWS\", e rilancia."
else
ok "accesso AWS: ${IDENTITA##*/}"
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
male "AWS CLI non installata: installala e rilancia questo script."
elif IDENTITA="$(aws sts get-caller-identity --query Arn --output text 2> /dev/null)" ; then
if [ -z "$UTENTE" ] || [ "${IDENTITA##*/}" = "$UTENTE" ] ; then
ok "accesso AWS: ${IDENTITA##*/}"
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
attento "su questo PC c'e' gia' una chiave AWS, ma e' di ${IDENTITA##*/}, non tua ($UTENTE)."
echo " Usandola, tutto quello che fai su AWS risulterebbe fatto da ${IDENTITA##*/}."
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 di ${IDENTITA##*/} e va disattivata."
echo " dal PC del suo proprietario e va disattivata."
chiedi_chiave_aws
fi
else
chiedi_chiave_aws
fi
fi
# --------------------------------------------------------------------------
titolo "3. Chiave SSH"
@@ -164,26 +236,38 @@ if [ -f "$CHIAVE_SSH" ] ; then
else
mkdir -p "$HOME/.ssh" && chmod 700 "$HOME/.ssh"
COMMENTO="${UTENTE:-$(whoami)}@oto"
ssh-keygen -q -t ed25519 -f "$CHIAVE_SSH" -C "$COMMENTO" -N "" \
&& ok "chiave creata: $CHIAVE_SSH"
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")"
if curl -s --max-time 10 -H "Authorization: token $(token_gitea)" "$GITEA_URL/api/v1/user/keys" \
| grep -qF "${PUBBLICA#* }" ; then
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="$(curl -s -o /dev/null --max-time 10 -w '%{http_code}' -X POST \
-H "Authorization: token $(token_gitea)" -H 'Content-Type: application/json' \
-d "{\"title\":\"oto $(hostname | tr -dc 'A-Za-z0-9-')\",\"key\":\"$PUBBLICA\"}" \
"$GITEA_URL/api/v1/user/keys")"
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
attento "non riesco a caricare la chiave su Gitea (HTTP $CODICE). Manda all'amministratore questa riga, e' la parte pubblica:"
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
@@ -197,6 +281,7 @@ titolo "4. Nomi dei server in ~/.ssh/config"
# 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 <<<"
@@ -225,9 +310,13 @@ Host $1
done
BLOCCO="$BLOCCO$FINE_BLOCCO"
{ 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"
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"
@@ -237,16 +326,26 @@ titolo "5. Chiavi dei server in ~/.ssh/known_hosts"
# 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.
# 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
grep -v '^#' server.known_hosts | awk 'NF' >> "$NOTI"
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"
# --------------------------------------------------------------------------
echo " Ora: se su LastPass hai la scheda \"OTO > VPN HK ${UTENTE:-nome.cognome}\", accendi la VPN (file e credenziali sono li'); poi torna da Claude."
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."
+50 -19
View File
@@ -8,25 +8,38 @@
# Li mette in $CARTELLA_PROGETTI (di solito ~/Work/workspace/oto). Un progetto
# gia' scaricato non si tocca. Vedi solo i repository che l'amministratore ti
# ha dato: se ne manca uno, e' da chiedere a lui.
# Esce 1 se un progetto chiesto non e' stato scaricato.
set -u
cd "$(dirname "$0")" || exit 1
. ./comune.sh
TOKEN="$(token_gitea)"
if [ -z "$TOKEN" ] || [ -z "$(utente_gitea)" ] ; then
male "token Gitea mancante o non valido: lancia prima ./configura.sh"
if [ -z "$(token_gitea)" ] ; then
male "token Gitea mancante: lancia prima ./configura.sh"
exit 1
fi
# Tutti i repository visibili, pagina per pagina.
# Tutti i repository visibili, pagina per pagina. Ci si ferma solo su una
# pagina vuota vera ("[]"): un errore di Gitea a meta' non deve diventare
# "non vedi nessun progetto", ne' un elenco tagliato senza dirlo.
ELENCO=""
PAGINA=1
while : ; do
BLOCCO="$(curl -s --max-time 15 -H "Authorization: token $TOKEN" \
"$GITEA_URL/api/v1/user/repos?limit=50&page=$PAGINA" \
| grep -o '"full_name":"[^"]*"' | sed 's/"full_name":"\(.*\)"/\1/')"
[ -z "$BLOCCO" ] && break
RISPOSTA="$(api_gitea "/user/repos?limit=50&page=$PAGINA")"
CODICE="${RISPOSTA##*$'\n'}"
CORPO="${RISPOSTA%$'\n'*}"
case "$CODICE" in
200) ;;
401|403) male "Gitea non accetta il tuo token: rilancia ./configura.sh" ; exit 1 ;;
000) male "Gitea ($GITEA_URL) non risponde: controlla la connessione e rilancia" ; exit 1 ;;
*) male "Gitea ha risposto HTTP $CODICE alla pagina $PAGINA dell'elenco: rilancia, e se si ripete avvisa l'amministratore" ; exit 1 ;;
esac
[ "$(printf '%s' "$CORPO" | tr -d '[:space:]')" = "[]" ] && break
BLOCCO="$(printf '%s' "$CORPO" | grep -o '"full_name"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"\([^"]*\)"$/\1/')"
if [ -z "$BLOCCO" ] ; then
male "non capisco la risposta di Gitea alla pagina $PAGINA dell'elenco: avvisa l'amministratore"
exit 1
fi
ELENCO="$ELENCO
$BLOCCO"
PAGINA=$((PAGINA + 1))
@@ -38,46 +51,64 @@ if [ -z "$ELENCO" ] ; then
exit 0
fi
# I nomi si scelgono corti (apulia_ecommerce) o completi (oto/apulia_ecommerce).
# Si tiene il nome completo: lo stesso nome corto in due organizzazioni e'
# ambiguo, e lo si dice invece di prendere il primo.
if [ "$#" -gt 0 ] ; then
SCELTI="$*"
else
titolo "Progetti che puoi scaricare"
printf '%s\n' "$ELENCO" | sed 's#.*/##' | nl -w3 -s'. '
printf '%s\n' "$ELENCO" | nl -w3 -s'. '
echo
read -r -p "Numeri o nomi, separati da spazio (invio = nessuno, 'tutti' = tutti): " RISPOSTA
[ -z "$RISPOSTA" ] && exit 0
if [ "$RISPOSTA" = "tutti" ] ; then
SCELTI="$(printf '%s\n' "$ELENCO" | sed 's#.*/##')"
SCELTI="$ELENCO"
else
SCELTI=""
for R in $RISPOSTA ; do
case "$R" in
*[!0-9]*) SCELTI="$SCELTI $R" ;;
*) SCELTI="$SCELTI $(printf '%s\n' "$ELENCO" | sed -n "${R}p" | sed 's#.*/##')" ;;
*) SCELTI="$SCELTI $(printf '%s\n' "$ELENCO" | sed -n "${R}p")" ;;
esac
done
fi
fi
ERRORI=0
mkdir -p "$CARTELLA_PROGETTI"
titolo "Scarico in $CARTELLA_PROGETTI"
for NOME in $SCELTI ; do
COMPLETO="$(printf '%s\n' "$ELENCO" | grep -E "/$NOME\$" | head -n 1)"
if [ -z "$COMPLETO" ] ; then
male "$NOME: non lo vedi su Gitea"
# Confronto esatto, non una regex: il nome lo scrive la persona.
COMPLETI="$(printf '%s\n' "$ELENCO" | awk -v n="$NOME" '$0 == n || substr($0, index($0, "/") + 1) == n')"
if [ -z "$COMPLETI" ] ; then
male "$NOME: non lo vedi su Gitea (se ti serve, chiedilo all'amministratore)"
ERRORI=$((ERRORI + 1))
continue
fi
if [ -d "$CARTELLA_PROGETTI/$NOME/.git" ] ; then
ok "$NOME: gia' scaricato"
if [ "$(printf '%s\n' "$COMPLETI" | wc -l)" -gt 1 ] ; then
male "$NOME: c'e' in piu' di un'organizzazione ($(printf '%s ' $COMPLETI)): scrivi il nome completo"
ERRORI=$((ERRORI + 1))
continue
fi
if git clone -q "$GITEA_URL/$COMPLETO.git" "$CARTELLA_PROGETTI/$NOME" ; then
ok "$NOME"
CORTO="${COMPLETI#*/}"
if [ -d "$CARTELLA_PROGETTI/$CORTO/.git" ] ; then
ok "$CORTO: gia' scaricato"
continue
fi
if git clone -q "$GITEA_URL/$COMPLETI.git" "$CARTELLA_PROGETTI/$CORTO" ; then
ok "$CORTO"
else
male "$NOME: git clone non riuscito"
male "$CORTO: git clone non riuscito (il messaggio di git e' qui sopra)"
ERRORI=$((ERRORI + 1))
fi
done
echo
if [ "$ERRORI" != "0" ] ; then
if [ "$ERRORI" = "1" ] ; then male "un progetto non scaricato (sopra, in rosso)"
else male "$ERRORI progetti non scaricati (sopra, in rosso)" ; fi
exit 1
fi
echo " In ogni progetto: ./start.sh (progetti con pannello) oppure bin/local-init.sh"
echo " (Drupal e WordPress). Apri claude nella cartella del progetto: ti spiega il resto."
+52 -21
View File
@@ -2,7 +2,8 @@
#
# DICE VERDE O ROSSO PER OGNI ACCESSO, e per ogni rosso cosa fare.
#
# ./verifica.sh
# ./verifica.sh # per chi ha la VPN
# ./verifica.sh --senza-vpn # per chi non la ha: staging non raggiungibile e' giallo
#
# Non modifica niente e non chiede niente: si puo' lanciare quando si vuole,
# anche da Claude. Esce 0 se e' tutto verde, 1 altrimenti.
@@ -11,6 +12,15 @@ set -u
cd "$(dirname "$0")" || exit 1
. ./comune.sh
# Chi non ha la VPN non arriva a staging, ed e' giusto cosi': senza questa
# opzione quel rosso faceva uscire 1 anche un PC preparato bene per il suo ruolo.
SENZA_VPN=0
case "${1:-}" in
--senza-vpn) SENZA_VPN=1 ;;
"") ;;
*) echo "Uso: ./verifica.sh [--senza-vpn]" >&2 ; exit 2 ;;
esac
ROSSI=0
rosso () { male "$@" ; ROSSI=$((ROSSI + 1)) ; }
@@ -24,17 +34,44 @@ if c_e docker ; then
fi
if c_e claude ; then ok "Claude Code" ; else attento "Claude Code non installato (non obbligatorio, ma ti guida)" ; fi
titolo "Gitea"
UTENTE="$(utente_gitea)" ; ESITO_GITEA=$?
if [ -n "$UTENTE" ] ; then
ok "token valido (utente $UTENTE)"
# Si chiede a git la credenziale che userebbe davvero: un gestore qualsiasi
# configurato non vuol dire che abbia quella giusta, e un token vecchio
# rimasto li' fa funzionare l'API ma non git clone.
GIT_PASSWORD="$(password_git_gitea)"
if [ "$GIT_PASSWORD" = "$(token_gitea)" ] ; then
ok "git entra su $GITEA_HOST con il tuo token"
elif [ -n "$GIT_PASSWORD" ] ; then
rosso "git usa un accesso a Gitea diverso dal tuo token (vecchio o di un altro): rilancia ./configura.sh, che lo sostituisce"
else
rosso "git non ha le credenziali di Gitea: rilancia ./configura.sh"
fi
unset GIT_PASSWORD
elif [ "$ESITO_GITEA" = "2" ] ; then
rosso "Gitea ($GITEA_URL) non risponde: controlla la connessione a internet e rilancia"
else
rosso "token Gitea mancante o non valido: rilancia ./configura.sh"
fi
titolo "AWS"
# Chi e' la persona lo dice Gitea: una chiave AWS di un altro sul PC vuol dire
# che tutto quello che fa su AWS risulta fatto da lui.
UTENTE="$(utente_gitea)"
if c_e aws && IDENTITA="$(aws sts get-caller-identity --query Arn --output text 2> /dev/null)" ; then
if [ -n "$UTENTE" ] && [ "${IDENTITA##*/}" != "$UTENTE" ] ; then
rosso "la chiave AWS di questo PC e' di ${IDENTITA##*/}, non tua ($UTENTE): rilancia ./configura.sh, che la sostituisce"
# che tutto quello che fa su AWS risulta fatto da lui. Senza Gitea non si puo'
# dire che la chiave sia sua, e quindi non e' verde.
AWS_OPZ=(--cli-connect-timeout 10 --cli-read-timeout 15)
CONFLITTI="$(aws_variabili_in_conflitto)"
[ -n "$CONFLITTI" ] && attento "nel terminale sono impostate $CONFLITTI: la AWS CLI usa quelle, non la chiave di ~/.aws/credentials"
if c_e aws && IDENTITA="$(AWS_MAX_ATTEMPTS=1 aws sts get-caller-identity "${AWS_OPZ[@]}" --query Arn --output text 2> /dev/null)" && [ -n "$IDENTITA" ] ; then
if [ -z "$UTENTE" ] ; then
rosso "c'e' una chiave AWS (${IDENTITA#arn:aws:iam::}), ma finche' Gitea non va non posso dire che sia tua"
elif aws_e_di "$IDENTITA" "$UTENTE" ; then
ok "accesso: $UTENTE"
else
ok "accesso: ${IDENTITA##*/}"
rosso "la chiave AWS di questo PC e' di ${IDENTITA#arn:aws:iam::}, non tua ($UTENTE nell'account OTO $AWS_ACCOUNT): rilancia ./configura.sh, che la sostituisce"
fi
if aws ssm get-parameter --region "$AWS_REGIONE" --name "$SSM_PROVA" --query Parameter.Name --output text > /dev/null 2>&1 ; then
if AWS_MAX_ATTEMPTS=1 aws ssm get-parameter "${AWS_OPZ[@]}" --region "$AWS_REGIONE" --name "$SSM_PROVA" --query Parameter.Name --output text > /dev/null 2>&1 ; then
ok "legge i segreti comuni su SSM"
else
rosso "non legge SSM: all'amministratore serve darti il ruolo di sviluppo"
@@ -43,20 +80,12 @@ else
rosso "AWS non configurato: rilancia ./configura.sh, che ti dice dove crearti la chiave nella console ($AWS_CONSOLE)"
fi
titolo "Gitea"
if [ -n "$UTENTE" ] ; then
ok "token valido (utente $UTENTE)"
if grep -q "@$GITEA_HOST" "$HOME/.git-credentials" 2> /dev/null || [ -n "$(git config --global credential.helper)" ] ; then
ok "git sa come entrare su $GITEA_HOST"
else
rosso "git non ha le credenziali di Gitea: rilancia ./configura.sh"
fi
else
rosso "token Gitea mancante o non valido: rilancia ./configura.sh"
fi
titolo "Chiave SSH"
if [ -f "$CHIAVE_SSH" ] ; then ok "$CHIAVE_SSH" ; else rosso "chiave mancante: rilancia ./configura.sh" ; fi
# I nomi con cui la cercano i deploy dei progetti
for ALTRA in $CHIAVI_SSH_PROGETTI ; do
[ -e "$ALTRA" ] || rosso "manca $ALTRA, che usano i deploy dei progetti: rilancia ./configura.sh"
done
titolo "VPN e server"
# La VPN si riconosce da una cosa sola: staging, che sta su rete privata,
@@ -85,11 +114,13 @@ entra () {
return 2
}
if porta_aperta "$STAGING_HOST" 22 ; then
ok "VPN attiva (staging raggiungibile)"
ok "staging raggiungibile (la VPN e' accesa)"
entra "$STAGING_NOME" ; ESITO=$?
if [ "$ESITO" = "0" ] ; then ok "entri su staging ($STAGING_NOME)"
elif [ "$ESITO" = "1" ] ; then rosso "staging non accetta la tua chiave: l'amministratore deve ancora aggiungerla"
else rosso "staging ($STAGING_NOME): $MOTIVO" ; fi
elif [ "$SENZA_VPN" = "1" ] ; then
attento "staging non raggiungibile: senza VPN e' normale"
else
rosso "staging non raggiungibile: la VPN e' accesa?"
fi