Setup base di un server Debian o Ubuntu appena installato: aggiornamento, utente amministratore, accesso SSH, Docker, Portainer, Git/GitHub, Claude Code, tmux e (opzionale) una cartella condivisa Samba.
Lo script è riprendibile: ogni passo completato viene registrato su disco. Se
si interrompe (errore, rete caduta, Ctrl+C, SSH chiuso) basta rilanciarlo:
salta quello che è già fatto e riparte dal passo interrotto con gli stessi
parametri.
| Requisito | Dettaglio |
|---|---|
| Sistema | Debian o Ubuntu (anche derivate con ID_LIKE=debian/ubuntu) |
| Privilegi | root (sudo) |
| Utente di lavoro | Un account non-root già esistente e con password impostata |
| Rete | Accesso a Internet (repository apt, Docker Hub, GitHub) |
Se l'utente non ha una password valida lo script si ferma. Impostala prima:
sudo passwd nomeutente# copia lo script sul server, poi:
chmod +x bootstrap-server.sh
sudo ./bootstrap-server.sh nomeutenteSe ometti il nome utente viene usato l'utente che ha lanciato sudo:
sudo ./bootstrap-server.shVengono poste due domande: se vuoi la cartella condivisa Samba (in caso
affermativo seguono nome, cartella, reti autorizzate e password) e se vuoi
installare Claude Code. Tutte le domande arrivano all'inizio: da lì in
poi l'installazione procede da sola. Senza un terminale interattivo (per esempio
dentro uno script) entrambi vengono saltati e restano installabili dopo, con
--samba e --claude-code.
Su una connessione SSH instabile conviene lanciarlo dentro tmux o screen:
sudo apt-get install -y tmux
tmux new -s setup
sudo ./bootstrap-server.sh nomeutente
# se cade la connessione: tmux attach -t setup| Comando | Cosa fa |
|---|---|
sudo ./bootstrap-server.sh [utente] |
Installazione o ripresa |
sudo ./bootstrap-server.sh --status |
Mostra i passi completati e quando |
sudo ./bootstrap-server.sh --portainer-token |
Mostra il token di setup di Portainer |
sudo ./bootstrap-server.sh --samba [utente] |
Configura/aggiorna una condivisione Samba in seguito |
sudo ./bootstrap-server.sh --claude-code |
Installa (o reinstalla) Claude Code in seguito |
sudo ./bootstrap-server.sh --reset |
Azzera lo stato: al prossimo avvio riparte da zero |
./bootstrap-server.sh --help |
Riassunto delle opzioni |
Non è possibile eseguire due istanze contemporaneamente: la seconda si ferma
con un messaggio (lock su /run/bootstrap-server.lock).
| # | Passo | Contenuto |
|---|---|---|
| 1 | Aggiornamento del sistema | apt-get update + full-upgrade, installa ca-certificates curl gnupg sudo openssh-server git tmux ncurses-term iproute2, pulizia pacchetti |
| 2 | Privilegi amministrativi | Utente nel gruppo sudo + regola sudo senza password in /etc/sudoers.d/90-<utente>-nopasswd (validata con visudo -c) |
| 3 | Accesso SSH | /etc/ssh/sshd_config.d/00-bootstrap.conf: PermitRootLogin no, PasswordAuthentication yes, PubkeyAuthentication yes. Ricarica ssh senza chiudere le sessioni aperte |
| 4 | Docker Engine | Repository ufficiale Docker, docker-ce, docker-ce-cli, containerd.io, plugin buildx e compose |
| 5 | Portainer | Portainer CE sulla 9443 oppure Portainer Agent sulla 9001 (vedi capitolo 6) |
| 6 | Git e GitHub | git config di base, GitHub CLI (gh) opzionale, chiave SSH dedicata ~/.ssh/id_ed25519_github + voce in ~/.ssh/config |
| 7 | Claude Code | Solo se richiesto: repository apt di Anthropic con verifica dell'impronta della chiave, pacchetto claude-code (vedi capitolo 8) |
| 8 | Permessi | Utente nel gruppo docker (docker senza sudo), git config --system safe.directory '*', /opt scrivibile dall'utente (vedi capitolo 7) |
| 9 | tmux | Crea ~/.tmux.conf con mouse, cronologia 50000 righe, numerazione da 1 (non tocca un file già esistente) |
| 10 | Samba | Solo se richiesto: pacchetti, cartella, condivisione, utente Samba, regole UFW |
Cosa non fa: non attiva UFW, non installa un reverse proxy, non richiede certificati TLS, non disabilita l'accesso SSH con password.
A fine installazione lo script stampa:
- indirizzo IP e comando di accesso SSH;
- URL di Portainer e token di setup;
- promemoria che
/optè scrivibile senzasudo; - chiave pubblica da incollare su GitHub;
- versione di Claude Code e comandi per il primo accesso, se installato;
- dati della condivisione Samba (percorsi Windows / macOS / Linux);
- avvisi: rientrare in SSH per usare
dockersenzasudo, riavvio richiesto, ecc.
Tutto l'output è anche in /var/log/bootstrap-server.log (leggibile solo da root):
sudo less /var/log/bootstrap-server.logDalle versioni 2.39.4 / 2.43 Portainer chiede un setup token per creare il primo amministratore: senza token la pagina iniziale non si completa. Serve a impedire che qualcun altro, raggiungendo il server prima di te, si prenda l'istanza appena avviata. Documentazione: https://docs.portainer.io/faqs/installing/setup-token
Normalmente Portainer genera il token da solo e lo scrive nei log. Questo
script fa una cosa diversa e più comoda: lo genera lui e lo passa al
container con --setup-token. Conseguenze utili:
- il token è noto prima ancora che il container parta e viene stampato a video;
- resta lo stesso anche dopo un riavvio del container: se scadono i 5 minuti non devi rileggere niente, riavvii e riusi lo stesso token;
- viene salvato in
/var/lib/bootstrap-server/portainer-setup-token(solo root).
sudo ./bootstrap-server.sh --portainer-tokenOppure direttamente:
sudo cat /var/lib/bootstrap-server/portainer-setup-tokenSe il container è stato creato prima di questa versione dello script (token generato da Portainer), leggilo dai log:
docker logs portainer 2>&1 | grep setup_token- Apri
https://IP-DEL-SERVER:9443(certificato autofirmato: il browser avvisa, è normale). - Nella pagina iniziale incolla il token, poi crea nome utente e password dell'amministratore.
- Hai 5 minuti dall'avvio del container. Se scadono:
docker restart portainer
# poi riusa lo stesso tokenIl token è monouso: dopo la creazione dell'amministratore non serve più.
PORTAINER_MODE |
Effetto |
|---|---|
server (predefinito) |
Portainer CE completo, porta 9443 (HTTPS), volume portainer_data |
agent |
Solo l'agente, porta 9001: il server va aggiunto da un Portainer centrale |
none |
Portainer non viene installato |
In modalità agent il token non c'entra: l'agente non ha interfaccia.
# predefinito: token casuale generato dallo script
sudo PORTAINER_SETUP_TOKEN=auto ./bootstrap-server.sh utente
# token scelto da te (min 8 caratteri: lettere, numeri, . _ -)
sudo PORTAINER_SETUP_TOKEN=IlMioToken-2026 ./bootstrap-server.sh utente
# nessun token (solo su rete fidata: chi raggiunge la 9443 può prendersi l'istanza)
sudo PORTAINER_SETUP_TOKEN=none ./bootstrap-server.sh utenteSulle versioni di Portainer che non conoscono l'opzione lo script se ne accorge da solo (interroga l'immagine) e non passa nulla.
docker ps --filter name=portainer # stato
docker logs -f portainer # log in tempo reale
docker restart portainer # riavvio (il token resta lo stesso)
# aggiornamento all'ultima versione (dati e utenti restano nel volume)
docker pull portainer/portainer-ce:lts
docker rm -f portainer
sudo rm /var/lib/bootstrap-server/portainer.done
sudo ./bootstrap-server.shIl segnalibro portainer.done va cancellato: senza, lo script considera il
passo già fatto e non ricrea il container.
Attenzione: le porte pubblicate da Docker scavalcano UFW. Portainer sulla 9443 resta raggiungibile anche con il firewall attivo. Su un server esposto a Internet proteggilo con il firewall del provider, con una VPN, oppure pubblicalo solo in locale e mettici davanti un reverse proxy:
sudo PORTAINER_BIND=127.0.0.1 ./bootstrap-server.sh utente
/opt è il posto giusto dove tenere gli stack Docker e le applicazioni
installate a mano. Di serie appartiene però solo a root, quindi dall'utente:
$ mkdir /opt/miostack
mkdir: cannot create directory '/opt/miostack': Permission denied
Lo script risolve la cosa nel passo Permessi: assegna /opt al gruppo
dell'utente e gli mette il bit setgid.
# quello che fa lo script:
chgrp <gruppo-utente> /opt
chmod 2775 /optRisultato:
drwxrwsr-x root mario /opt
- l'utente crea cartelle e file in
/optsenzasudo; - il setgid (la
s) fa ereditare il gruppo a tutto quello che viene creato dentro: comodo se in futuro aggiungi altri utenti allo stesso gruppo; - il contenuto già presente non viene toccato: le cartelle installate da
root (per esempio da un pacchetto
.deb) restano di root e protette.
Verifica:
ls -ld /opt
mkdir /opt/prova && rmdir /opt/provaIl passo "Permessi" risulta già completato, quindi va rifatto esplicitamente:
sudo rm /var/lib/bootstrap-server/permissions.done
sudo ./bootstrap-server.shOppure, se preferisci farlo a mano subito:
sudo chgrp "$(id -gn)" /opt
sudo chmod 2775 /optsudo OPT_WRITABLE=no ./bootstrap-server.sh utentePer tornare alla situazione originale di Debian/Ubuntu:
sudo chown root:root /opt
sudo chmod 755 /optNota: se il gruppo principale dell'utente non è personale (non ha cioè lo stesso nome dell'utente), scriveranno in
/opttutti i membri di quel gruppo. Lo script te lo segnala durante l'installazione.
Claude Code è l'assistente di Anthropic da riga di comando. Lo script chiede all'avvio se installarlo e, se rispondi di no, puoi installarlo in qualsiasi momento in seguito.
Dal repository apt ufficiale di Anthropic, con lo stesso metodo usato per
Docker e GitHub CLI: chiave di firma in /etc/apt/keyrings/, repository in
/etc/apt/sources.list.d/, pacchetto claude-code.
Prima di aggiungere il repository lo script verifica l'impronta della chiave contro quella pubblicata da Anthropic:
31DDDE24DDFAB679F42D7BD2BAA929FF1A7ECACE
Se non corrisponde, la chiave viene cancellata, il repository non viene aggiunto e l'installazione si ferma con un errore. È una difesa contro una chiave sostituita lungo il percorso.
L'installazione è di sistema (/usr/bin/claude): vale per tutti gli utenti.
Architetture supportate: amd64 e arm64; su altre lo script avvisa e salta.
sudo ./bootstrap-server.sh --claude-codeFunziona anche se al primo giro avevi risposto di no: forza il passo, lo rifà e mostra il riepilogo.
claude # avvia la sessione; il primo avvio chiede di accedere
claude --version # versione installata
claude doctor # diagnostica di installazione e impostazioniServe un account con piano Pro, Max, Team, Enterprise oppure Console: il piano gratuito di Claude.ai non include Claude Code. In alternativa si può usare un provider terzo (Amazon Bedrock, Google Cloud, Microsoft Foundry).
Documentazione: https://code.claude.com/docs/en/setup
L'installazione da repository apt non si aggiorna da sola: segue il normale aggiornamento di sistema.
sudo apt update && sudo apt upgrade claude-codeCLAUDE_CODE_CHANNEL |
Effetto |
|---|---|
stable (predefinito) |
Versione di circa una settimana fa, senza le release con regressioni gravi |
latest |
Ogni versione appena esce |
sudo CLAUDE_CODE_CHANNEL=latest ./bootstrap-server.sh --claude-codePer cambiare canale su un server già configurato, rilancia il comando qui sopra: il file del repository viene riscritto con il nuovo canale.
sudo CLAUDE_CODE_INSTALL=no ./bootstrap-server.sh utentesudo apt remove claude-code
sudo rm /etc/apt/sources.list.d/claude-code.list /etc/apt/keyrings/claude-code.ascSi passano davanti al comando. Vengono salvate in
/var/lib/bootstrap-server/config e riusate automaticamente alla ripresa
(le password non vengono mai salvate).
Priorità: variabile passata ora → valore salvato → valore predefinito.
| Variabile | Valori | Predefinito | Note |
|---|---|---|---|
GIT_NAME |
testo | vuoto | git config --global user.name |
GIT_EMAIL |
testo | vuoto | git config --global user.email |
INSTALL_GH_CLI |
yes / no |
yes |
Installa la GitHub CLI (gh) |
GIT_SAFE_DIRECTORY_ALL |
yes / no |
yes |
Evita l'errore "dubious ownership" tra utente e sudo |
OPT_WRITABLE |
yes / no |
yes |
Rende /opt scrivibile dall'utente senza sudo (capitolo 7) |
CLAUDE_CODE_INSTALL |
ask / yes / no |
ask |
Installa Claude Code (senza terminale interattivo: no) |
CLAUDE_CODE_CHANNEL |
stable / latest |
stable |
Canale di rilascio del pacchetto claude-code |
| Variabile | Valori | Predefinito |
|---|---|---|
PORTAINER_MODE |
server / agent / none |
server |
PORTAINER_BIND |
indirizzo su cui pubblicare | 0.0.0.0 |
PORTAINER_EDGE_PORT |
yes / no |
no (se yes pubblica anche la 8000 per gli Edge Agent) |
PORTAINER_SETUP_TOKEN |
auto / none / valore |
auto |
PORTAINER_IMAGE |
immagine | portainer/portainer-ce:lts |
PORTAINER_AGENT_IMAGE |
immagine | portainer/agent:lts |
| Variabile | Valori | Predefinito |
|---|---|---|
SAMBA_ENABLE |
ask / yes / no |
ask (senza terminale interattivo: no) |
SAMBA_SHARE_NAME |
nome | condivisa |
SAMBA_PATH |
percorso assoluto | /srv/samba/<nome> |
SAMBA_HOSTS_ALLOW |
reti separate da spazio, oppure all |
reti private rilevate |
SAMBA_PASSWORD |
min 8 caratteri | — (chiesta a terminale, mai salvata su disco) |
sudo GIT_NAME="Mario Rossi" GIT_EMAIL="mario@example.com" \
PORTAINER_MODE=server PORTAINER_BIND=127.0.0.1 \
SAMBA_ENABLE=yes SAMBA_SHARE_NAME=dati \
SAMBA_HOSTS_ALLOW="192.168.1.0/24" SAMBA_PASSWORD='PasswordLunga1' \
./bootstrap-server.sh mario| Percorso | Contenuto |
|---|---|
/var/lib/bootstrap-server/ |
Stato dell'installazione (permessi 700) |
/var/lib/bootstrap-server/config |
Parametri salvati (600) |
/var/lib/bootstrap-server/portainer-setup-token |
Token di setup di Portainer (600) |
/var/lib/bootstrap-server/<passo>.done |
Segnalibro di ogni passo completato |
/var/log/bootstrap-server.log |
Log completo (600) |
/etc/sudoers.d/90-<utente>-nopasswd |
sudo senza password |
/etc/ssh/sshd_config.d/00-bootstrap.conf |
Impostazioni SSH |
/etc/apt/sources.list.d/docker.list |
Repository Docker |
/etc/apt/sources.list.d/github-cli.list |
Repository GitHub CLI |
/etc/apt/sources.list.d/claude-code.list |
Repository Claude Code |
/etc/apt/keyrings/claude-code.asc |
Chiave di firma di Claude Code (impronta verificata) |
~/.ssh/id_ed25519_github[.pub] |
Chiave SSH per GitHub |
~/.ssh/config |
Voce Host github.com |
~/.tmux.conf |
Configurazione tmux |
/etc/samba/shares.d/<nome>.conf |
Una condivisione per file |
/etc/samba/bootstrap-shares.conf |
Indice delle condivisioni, incluso in smb.conf |
/srv/samba/<nome> |
Cartella condivisa (se non ne indichi un'altra) |
/opt |
Non creata, ma resa scrivibile dall'utente: gruppo dell'utente, permessi 2775 |
# accesso SSH con password (da un altro computer)
ssh utente@IP-DEL-SERVER
# root senza password, dall'utente
sudo -i
# Docker senza sudo (prima esci e rientra in SSH: il gruppo è nuovo)
docker run --rm hello-world
docker compose version
# Portainer
docker ps --filter name=portainer
sudo ./bootstrap-server.sh --portainer-token
# /opt scrivibile senza sudo
ls -ld /opt # atteso: drwxrwsr-x root <tuogruppo>
mkdir /opt/prova && rmdir /opt/prova
# Claude Code (se installato)
claude --version
claude doctor
# GitHub: incolla prima la chiave su https://github.com/settings/keys
cat ~/.ssh/id_ed25519_github.pub
ssh -T git@github.com # risposta attesa: "Hi <utente>! You've successfully authenticated"
gh auth login # in alternativa, con la GitHub CLI
# Samba
testparm -s # configurazione valida?
smbclient -L localhost -U utenteAccesso alla cartella condivisa:
- Windows:
\\IP-DEL-SERVER\nomecondivisione - macOS / Linux:
smb://IP-DEL-SERVER/nomecondivisione - Utente: il tuo utente Linux, con la password Samba (è separata da quella di
sistema). Per cambiarla:
sudo smbpasswd utente
sudo ./bootstrap-server.sh --status # cosa è già stato fatto
sudo ./bootstrap-server.sh # riprende da dove si era fermatoPer rifare un solo passo, cancella il suo segnalibro e rilancia:
sudo rm /var/lib/bootstrap-server/portainer.done
sudo ./bootstrap-server.shNomi dei segnalibri: system_update, admin_user, ssh_access, docker,
portainer, git_github, claude_code, permissions, tmux, samba.
Per ricreare Portainer da zero (per esempio per cambiare token o porta):
docker rm -f portainer
sudo rm /var/lib/bootstrap-server/portainer.done
sudo ./bootstrap-server.shIl volume
portainer_datanon viene toccato: utenti e ambienti restano. Se elimini anche il volume (docker volume rm portainer_data) riparti da un'istanza vuota e servirà di nuovo il token.
Per ricominciare tutto da capo (non disinstalla nulla, azzera solo lo stato):
sudo ./bootstrap-server.sh --reset| Sintomo | Causa e soluzione |
|---|---|
| Portainer: "Your Portainer instance timed out" | Passati i 5 minuti: docker restart portainer, poi riusa lo stesso token |
| La pagina di Portainer chiede un token che non hai | sudo ./bootstrap-server.sh --portainer-token; se vuoto, docker logs portainer 2>&1 | grep setup_token |
| Il token non viene accettato | È monouso e legato all'avvio corrente: riavvia il container e riprova |
docker: permission denied |
Il gruppo docker vale dalla sessione successiva: esci e rientra in SSH (oppure newgrp docker) |
mkdir: /opt/...: Permission denied |
Passo "Permessi" non ancora eseguito con questa versione: sudo rm /var/lib/bootstrap-server/permissions.done e rilancia lo script (capitolo 7) |
Permission denied dentro una cartella di /opt creata da root |
Solo /opt è condiviso, non il suo contenuto preesistente: sudo chown -R utente:gruppo /opt/quella-cartella |
| SSH chiede una chiave e rifiuta la password | Un file in /etc/ssh/sshd_config.d/ con numero più alto, o un blocco Match, sovrascrive l'impostazione: sudo sshd -T | grep passwordauthentication |
| L'utente non ha password | sudo passwd utente, poi rilancia lo script |
| Lo script si ferma su apt/dpkg | Rilancialo: ripara dpkg e riprende. Se persiste: sudo dpkg --configure -a |
| "Un'altra esecuzione è già in corso" | C'è davvero un'altra istanza, oppure una sessione morta: sudo rm /run/bootstrap-server.lock |
| Samba non raggiungibile | Rete non autorizzata: sudo ./bootstrap-server.sh --samba e indica la rete giusta (es. 192.168.1.0/24) |
| Password Samba dimenticata | sudo smbpasswd utente |
claude: command not found |
Non installato (o hai risposto di no): sudo ./bootstrap-server.sh --claude-code |
| "Chiave di Claude Code non attendibile" | L'impronta scaricata non è quella pubblicata da Anthropic: non installare, controlla la rete (proxy che intercetta HTTPS) e riprova |
NO_PUBKEY BAA929FF1A7ECACE durante apt update |
Il file della chiave è mancante o corrotto: sudo ./bootstrap-server.sh --claude-code lo riscarica e lo verifica |
| Claude Code chiede un abbonamento | Serve un piano Pro, Max, Team, Enterprise o Console: il piano gratuito di Claude.ai non lo include |
| Portainer raggiungibile nonostante UFW | Le porte Docker scavalcano UFW: usa PORTAINER_BIND=127.0.0.1 + reverse proxy, o il firewall del provider |
- Il login SSH diretto di root è disabilitato; si entra con l'utente e poi
sudo -i. sudosenza password è comodo ma significa che chi ottiene la sessione dell'utente è root: la password dell'utente va scelta lunga e, meglio ancora, passa all'accesso con chiave SSH.- Il token di setup e il log contengono informazioni sensibili: entrambi sono leggibili solo da root.
- Portainer ha accesso a
/var/run/docker.sock: chi entra in Portainer controlla il server. Non esporlo su Internet senza una protezione davanti. /optdiventa scrivibile dall'utente: è una comodità, non una porta aperta (nessun altro account ci guadagna accesso), ma ricorda che i file che metti lì sono modificabili senzasudo. Se/optdeve restare solo di root usaOPT_WRITABLE=no.- L'accesso a Internet della porta 9443 (e 445 per Samba) va valutato caso per caso: lo script non attiva UFW al posto tuo.