/var/lib/ci/backups was root:root because ci-backup-template.service runs as root and mkdir(exist_ok=True) created it before ci-runner ever touched it, so every 7z backup failed with errno=13. Chown to ci-runner:ci-runner + 750, and stop the drift from coming back: the setup script now creates+chmods the dir, and the systemd unit gets an ExecStartPre that (re)installs it with the right owner on every start.
357 lines
16 KiB
Bash
Executable File
357 lines
16 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# setup-host-linux.sh — Bootstrap CI host Linux Mint (Fase B, Step B1)
|
|
#
|
|
# Equivalente di Setup-Host.ps1 per l'host Linux.
|
|
# Eseguire come root: sudo bash setup-host-linux.sh -d /dev/sdf1 [opzioni]
|
|
#
|
|
# Cosa fa:
|
|
# 1. Crea l'utente di servizio ci-runner
|
|
# 2. Monta la partizione CI su /var/lib/ci via fstab (boot automatico)
|
|
# Se la partizione ha già una sottocartella ci/, la riorganizza
|
|
# portando i contenuti alla root della partizione (nessun dato perso)
|
|
# 3. Crea il layout directory e permessi
|
|
# 4. Installa Python 3.11+ e crea venv produzione in /opt/ci/venv
|
|
# 5. (Opzionale) Clona il repo e installa il package nel venv
|
|
# 6. (Opt-in, --with-pwsh) Installa PowerShell Core
|
|
# 7. Installa l'alias shell `ci` a livello di sistema (/etc/profile.d/)
|
|
# 8. Installa i servizi VMware host (unit systemd native): caricano vmmon/vmnet
|
|
# e avviano la rete vmnet (DHCP/NAT) a ogni boot, e ricostruiscono i moduli
|
|
# dopo un upgrade del kernel. Sostituisce il flaky sysv vmware.service.
|
|
# 9. Autorizza ci-runner sulla sessione X del desktop user (xhost persistente)
|
|
# così `ci vmware` può aprire la GUI di VMware Workstation.
|
|
# L'host Linux NON ha più bisogno di pwsh per il normale funzionamento
|
|
# (orchestratore Python). Installalo solo se ti serve per script legacy.
|
|
|
|
set -euo pipefail
|
|
|
|
# ── Defaults ─────────────────────────────────────────────────────────────────
|
|
CI_DISK=""
|
|
CI_ROOT="/var/lib/ci"
|
|
CI_USER="ci-runner"
|
|
OPT_CI="/opt/ci"
|
|
REPO_URL="https://gitea.emulab.it/Simone/local-ci-cd-system.git"
|
|
|
|
SKIP_PYTHON=false
|
|
WITH_PWSH=false
|
|
SKIP_CLONE=false
|
|
DESKTOP_USER="${SUDO_USER:-}"
|
|
SKIP_GUI_ACCESS=false
|
|
|
|
# ── Colori ───────────────────────────────────────────────────────────────────
|
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
|
|
info() { echo -e "${GREEN}==>${NC} $*"; }
|
|
warn() { echo -e "${YELLOW} ! $*${NC}"; }
|
|
die() { echo -e "${RED}ERROR: $*${NC}" >&2; exit 1; }
|
|
|
|
# ── Uso ──────────────────────────────────────────────────────────────────────
|
|
usage() {
|
|
cat <<EOF
|
|
Uso: sudo bash $(basename "$0") -d <disco> [opzioni]
|
|
|
|
Obbligatorio:
|
|
-d <disco> Partizione CI (es. /dev/sdf1 o /dev/sdf)
|
|
|
|
Opzionali:
|
|
-r <path> CI root (default: /var/lib/ci)
|
|
-u <utente> Utente di servizio (default: ci-runner)
|
|
--skip-python Salta installazione Python/venv
|
|
--skip-clone Salta clone repo + pip install
|
|
--with-pwsh Installa PowerShell Core (opt-in; NON installato di default).
|
|
L'host Linux non ha più bisogno di pwsh per il normale
|
|
funzionamento — usa questo flag solo per script legacy.
|
|
--skip-pwsh DEPRECATO: no-op. pwsh non è più installato di default.
|
|
--desktop-user <nome>
|
|
Desktop user da autorizzare sulla sessione X per la GUI di
|
|
VMware (default: \$SUDO_USER). Serve per 'ci vmware'.
|
|
--skip-gui-access
|
|
Salta l'autorizzazione X (host headless / nessun desktop).
|
|
|
|
Esempi:
|
|
sudo bash setup-host-linux.sh -d /dev/sdf1
|
|
sudo bash setup-host-linux.sh -d /dev/sdf1 --with-pwsh
|
|
sudo bash setup-host-linux.sh -d /dev/sdf1 --skip-clone
|
|
sudo bash setup-host-linux.sh -d /dev/sdf1 --desktop-user simone
|
|
EOF
|
|
exit 1
|
|
}
|
|
|
|
# ── Parse args ───────────────────────────────────────────────────────────────
|
|
[[ $# -eq 0 ]] && usage
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
-d) CI_DISK="$2"; shift 2 ;;
|
|
-r) CI_ROOT="$2"; shift 2 ;;
|
|
-u) CI_USER="$2"; shift 2 ;;
|
|
--skip-python) SKIP_PYTHON=true; shift ;;
|
|
--with-pwsh) WITH_PWSH=true; shift ;;
|
|
--skip-pwsh)
|
|
echo "WARN: --skip-pwsh è deprecato e non ha più effetto: pwsh non è installato di default (usa --with-pwsh per installarlo)." >&2
|
|
shift ;;
|
|
--skip-clone) SKIP_CLONE=true; shift ;;
|
|
--desktop-user) DESKTOP_USER="$2"; shift 2 ;;
|
|
--skip-gui-access) SKIP_GUI_ACCESS=true; shift ;;
|
|
-h|--help) usage ;;
|
|
*) die "Parametro sconosciuto: $1"; ;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$CI_DISK" ]] && die "-d <disco> è obbligatorio"
|
|
[[ $EUID -ne 0 ]] && die "Eseguire come root: sudo bash $0 ..."
|
|
[[ ! -b "$CI_DISK" ]] && die "$CI_DISK non è un block device"
|
|
|
|
echo ""
|
|
echo "╔══════════════════════════════════════════════════════╗"
|
|
echo "║ CI Host Setup — Linux Mint (Fase B / B1) ║"
|
|
echo "╚══════════════════════════════════════════════════════╝"
|
|
echo " Disco CI : $CI_DISK"
|
|
echo " CI root : $CI_ROOT"
|
|
echo " Utente : $CI_USER"
|
|
echo ""
|
|
|
|
# ── Step 1: utente di servizio ────────────────────────────────────────────────
|
|
info "[1/9] Utente di servizio $CI_USER"
|
|
if id "$CI_USER" &>/dev/null; then
|
|
warn "Utente $CI_USER esiste già — skip"
|
|
else
|
|
useradd -r -m -s /bin/bash "$CI_USER"
|
|
echo " Creato utente $CI_USER"
|
|
fi
|
|
|
|
# ── Step 2: mount partizione CI ───────────────────────────────────────────────
|
|
info "[2/9] Mount $CI_DISK → $CI_ROOT"
|
|
|
|
if mountpoint -q "$CI_ROOT" 2>/dev/null; then
|
|
warn "$CI_ROOT già montato — skip riorganizzazione e fstab"
|
|
else
|
|
# Recupera UUID e filesystem
|
|
UUID=$(blkid -s UUID -o value "$CI_DISK" 2>/dev/null)
|
|
FS_TYPE=$(blkid -s TYPE -o value "$CI_DISK" 2>/dev/null)
|
|
[[ -z "$UUID" ]] && die "Impossibile determinare UUID di $CI_DISK"
|
|
[[ -z "$FS_TYPE" ]] && die "Impossibile determinare filesystem di $CI_DISK"
|
|
echo " UUID=$UUID TYPE=$FS_TYPE"
|
|
|
|
# Mount temporaneo per eventuale riorganizzazione
|
|
TMP_MNT=$(mktemp -d /mnt/ci-setup-XXXXXX)
|
|
mount "$CI_DISK" "$TMP_MNT"
|
|
|
|
if [[ -d "$TMP_MNT/ci" ]]; then
|
|
echo " Trovata sottocartella ci/ — riorganizzazione in corso..."
|
|
# Sposta tutto il contenuto di ci/ alla root della partizione
|
|
shopt -s dotglob nullglob
|
|
for item in "$TMP_MNT/ci/"*; do
|
|
name=$(basename "$item")
|
|
if [[ -e "$TMP_MNT/$name" ]]; then
|
|
warn " Conflitto: $name già esiste alla root — skip spostamento"
|
|
else
|
|
mv "$item" "$TMP_MNT/"
|
|
echo " Spostato: ci/$name → /"
|
|
fi
|
|
done
|
|
shopt -u dotglob nullglob
|
|
if [[ -z "$(ls -A "$TMP_MNT/ci" 2>/dev/null)" ]]; then
|
|
rmdir "$TMP_MNT/ci"
|
|
echo " Rimossa cartella ci/ vuota"
|
|
else
|
|
warn " ci/ non è vuota dopo lo spostamento — controlla manualmente"
|
|
fi
|
|
else
|
|
echo " Nessuna sottocartella ci/ trovata — nessuna riorganizzazione necessaria"
|
|
fi
|
|
|
|
umount "$TMP_MNT"
|
|
rmdir "$TMP_MNT"
|
|
|
|
# Mount point e fstab
|
|
mkdir -p "$CI_ROOT"
|
|
if grep -q "UUID=$UUID" /etc/fstab; then
|
|
warn "Entry fstab per UUID=$UUID già presente — skip"
|
|
else
|
|
echo "UUID=$UUID $CI_ROOT $FS_TYPE defaults,nofail 0 2" >> /etc/fstab
|
|
echo " Aggiunta entry fstab"
|
|
fi
|
|
|
|
mount -a
|
|
mountpoint -q "$CI_ROOT" || die "mount -a fallito per $CI_ROOT"
|
|
echo " Montato OK: $(df -h "$CI_ROOT" | tail -1)"
|
|
fi
|
|
|
|
# ── Step 3: layout directory e permessi ───────────────────────────────────────
|
|
info "[3/9] Layout directory e permessi"
|
|
|
|
# Installa acl se mancante
|
|
if ! command -v setfacl &>/dev/null; then
|
|
echo " Installazione pacchetto acl..."
|
|
apt-get install -y acl -qq
|
|
fi
|
|
|
|
# Tooling immagini (template deploy-win patch ISO + deploy-linux seed ISO):
|
|
# p7zip-full — 7z: estrae l'ISO Windows (UDF/Joliet) per il repack patch
|
|
# xorriso — repack ISO Windows bootable no-prompt
|
|
# genisoimage — build cloud-init seed ISO (deploy-linux)
|
|
for pkg in p7zip-full xorriso genisoimage; do
|
|
if ! dpkg -s "$pkg" &>/dev/null; then
|
|
echo " Installazione pacchetto $pkg..."
|
|
apt-get install -y "$pkg" -qq
|
|
fi
|
|
done
|
|
|
|
mkdir -p \
|
|
"$CI_ROOT"/{build-vms,artifacts,templates,backups,keys,logs,runner,iso} \
|
|
"$OPT_CI" \
|
|
/etc/ci/keys
|
|
|
|
chown -R "$CI_USER:$CI_USER" "$CI_ROOT" "$OPT_CI"
|
|
chmod 750 "$CI_ROOT" "$OPT_CI"
|
|
for d in build-vms artifacts templates backups keys logs runner iso; do
|
|
chmod 750 "$CI_ROOT/$d"
|
|
done
|
|
chmod 700 /etc/ci/keys
|
|
|
|
# ACL di default su build-vms (ci-runner eredita rwX sui nuovi file)
|
|
setfacl -d -m "u:$CI_USER:rwX" "$CI_ROOT/build-vms"
|
|
echo " Layout OK — ACL default su build-vms applicata"
|
|
|
|
# ── Step 4: Python 3.11+ e venv ──────────────────────────────────────────────
|
|
if [[ "$SKIP_PYTHON" == false ]]; then
|
|
info "[4/9] Python 3.11+ e venv produzione"
|
|
|
|
# Cerca la prima versione di Python >= 3.11 già installata
|
|
PYTHON_BIN=""
|
|
for cmd in python3 python3.13 python3.12 python3.11; do
|
|
if command -v "$cmd" &>/dev/null; then
|
|
ver=$("$cmd" -c 'import sys; v=sys.version_info; print(v.major*100+v.minor)' 2>/dev/null)
|
|
if [[ "${ver:-0}" -ge 311 ]]; then
|
|
PYTHON_BIN="$cmd"
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
|
|
if [[ -n "$PYTHON_BIN" ]]; then
|
|
echo " Python >= 3.11 già presente: $($PYTHON_BIN --version) ($PYTHON_BIN)"
|
|
apt-get install -y "python3-venv" git -qq 2>/dev/null || true
|
|
else
|
|
echo " Nessun Python >= 3.11 trovato — installazione python3.11..."
|
|
apt-get install -y python3.11 python3.11-venv git -qq
|
|
PYTHON_BIN="python3.11"
|
|
fi
|
|
|
|
if [[ -d "$OPT_CI/venv" ]]; then
|
|
warn "venv $OPT_CI/venv già esistente — skip"
|
|
else
|
|
"$PYTHON_BIN" -m venv "$OPT_CI/venv"
|
|
chown -R "$CI_USER:$CI_USER" "$OPT_CI/venv"
|
|
echo " venv creato in $OPT_CI/venv ($($PYTHON_BIN --version))"
|
|
fi
|
|
else
|
|
info "[4/9] Python/venv — SKIPPED"
|
|
fi
|
|
|
|
# ── Step 5: Clone repo e pip install ─────────────────────────────────────────
|
|
if [[ "$SKIP_CLONE" == false ]]; then
|
|
info "[5/9] Clone repo e pip install"
|
|
REPO_DIR="$OPT_CI/local-ci-cd-system"
|
|
if [[ -d "$REPO_DIR/.git" ]]; then
|
|
warn "Repo già presente in $REPO_DIR — skip clone (eseguire git pull manualmente)"
|
|
else
|
|
echo " Clone repo..."
|
|
sudo -u "$CI_USER" git clone "$REPO_URL" "$REPO_DIR"
|
|
fi
|
|
echo " pip install..."
|
|
sudo -u "$CI_USER" "$OPT_CI/venv/bin/pip" install -q -e "$REPO_DIR"
|
|
verify_out=$(sudo -u "$CI_USER" "$OPT_CI/venv/bin/python" -m ci_orchestrator --help 2>&1) \
|
|
|| die "ci_orchestrator non importabile"
|
|
echo " Verifica OK: ${verify_out%%$'\n'*}"
|
|
else
|
|
info "[5/9] Clone repo — SKIPPED"
|
|
fi
|
|
|
|
# ── Step 6: PowerShell Core (opt-in via --with-pwsh) ──────────────────────────
|
|
if [[ "$WITH_PWSH" == true ]]; then
|
|
info "[6/9] PowerShell Core"
|
|
if command -v pwsh &>/dev/null; then
|
|
warn "pwsh già installato: $(pwsh --version) — skip"
|
|
else
|
|
echo " Installazione PowerShell Core..."
|
|
apt-get install -y wget apt-transport-https software-properties-common -qq
|
|
# Linux Mint riporta la propria versione in VERSION_ID; serve quella Ubuntu base
|
|
if [[ -f /etc/upstream-release/lsb-release ]]; then
|
|
UBUNTU_VER=$(grep DISTRIB_RELEASE /etc/upstream-release/lsb-release | cut -d= -f2)
|
|
else
|
|
source /etc/os-release
|
|
UBUNTU_VER="$VERSION_ID"
|
|
fi
|
|
wget -q "https://packages.microsoft.com/config/ubuntu/${UBUNTU_VER}/packages-microsoft-prod.deb" \
|
|
-O /tmp/ms-prod.deb
|
|
dpkg -i /tmp/ms-prod.deb
|
|
apt-get update -qq 2>&1 || true # ignora errori di repo terzi (es. chiavi GPG scadute)
|
|
apt-get install -y powershell -qq
|
|
rm /tmp/ms-prod.deb
|
|
echo " Installato: $(pwsh --version)"
|
|
fi
|
|
else
|
|
info "[6/9] PowerShell Core — SKIPPED (opt-in con --with-pwsh)"
|
|
fi
|
|
|
|
# ── Step 7: alias shell `ci` (system-wide) ────────────────────────────────────
|
|
info "[7/9] Alias shell 'ci'"
|
|
ALIAS_SCRIPT="$OPT_CI/local-ci-cd-system/scripts/install-ci-alias.sh"
|
|
if [[ -x "$ALIAS_SCRIPT" ]]; then
|
|
# Installa /etc/profile.d/ci-alias.sh per tutti gli utenti login.
|
|
# Passa il venv/utente correnti così l'alias punta a questa installazione.
|
|
CI_PROD_PYTHON="$OPT_CI/venv/bin/python" CI_RUN_USER="$CI_USER" \
|
|
bash "$ALIAS_SCRIPT" --system
|
|
echo " Alias 'ci' installato — apri una nuova shell e prova: ci validate host"
|
|
else
|
|
warn "install-ci-alias.sh non trovato in $ALIAS_SCRIPT — skip (clona il repo, Step 5)"
|
|
fi
|
|
|
|
# ── Step 8: servizi VMware host (moduli + rete al boot) ───────────────────────
|
|
info "[8/9] Servizi VMware host (vmmon/vmnet + rete al boot)"
|
|
VMSVC_SCRIPT="$OPT_CI/local-ci-cd-system/scripts/install-vmware-host-services.sh"
|
|
if [[ -x "$VMSVC_SCRIPT" ]]; then
|
|
# Installa le unit systemd native che caricano vmmon/vmnet e avviano la rete
|
|
# vmnet (DHCP/NAT) a ogni boot, e ricostruiscono i moduli dopo un upgrade del
|
|
# kernel. Sostituisce il flaky sysv vmware.service. Vedi deploy/systemd/.
|
|
bash "$VMSVC_SCRIPT"
|
|
echo " Servizi VMware host installati — verifica: ip -o link show | grep vmnet"
|
|
else
|
|
warn "install-vmware-host-services.sh non trovato in $VMSVC_SCRIPT — skip (clona il repo, Step 5)"
|
|
fi
|
|
|
|
# ── Step 9: autorizzazione X per la GUI di VMware (xhost persistente) ──────────
|
|
info "[9/9] Accesso GUI VMware (xhost ci-runner)"
|
|
GUI_SCRIPT="$OPT_CI/local-ci-cd-system/scripts/install-vmware-gui-access.sh"
|
|
if [[ "$SKIP_GUI_ACCESS" == true ]]; then
|
|
info " SKIPPED (--skip-gui-access)"
|
|
elif [[ -z "$DESKTOP_USER" ]]; then
|
|
warn "Nessun desktop user (SUDO_USER vuoto) — skip. Riesegui con --desktop-user <nome>."
|
|
elif [[ -x "$GUI_SCRIPT" ]]; then
|
|
# Autorizza ci-runner sulla sessione X del desktop user così `ci vmware`
|
|
# può aprire la GUI. Grant reso persistente via autostart XDG.
|
|
CI_RUN_USER="$CI_USER" \
|
|
bash "$GUI_SCRIPT" --desktop-user "$DESKTOP_USER"
|
|
echo " Accesso GUI configurato per '$DESKTOP_USER' — prova: ci vmware"
|
|
else
|
|
warn "install-vmware-gui-access.sh non trovato in $GUI_SCRIPT — skip (clona il repo, Step 5)"
|
|
fi
|
|
|
|
# ── Riepilogo ─────────────────────────────────────────────────────────────────
|
|
echo ""
|
|
echo "╔══════════════════════════════════════════════════════╗"
|
|
echo "║ Setup completato ║"
|
|
echo "╚══════════════════════════════════════════════════════╝"
|
|
echo ""
|
|
echo " CI_ROOT : $CI_ROOT ($(df -h "$CI_ROOT" | awk 'NR==2{print $4}') liberi)"
|
|
echo " CI_USER : $CI_USER"
|
|
echo " venv : $OPT_CI/venv"
|
|
echo ""
|
|
echo "Passi successivi (manuali):"
|
|
echo " B2 — Validare template VM in $CI_ROOT/templates/"
|
|
echo " find $CI_ROOT/templates -name '*.vmx' -exec vmrun -T ws listSnapshots {} \\;"
|
|
echo " B3 — Copiare chiavi SSH in /etc/ci/keys/ e configurare keyring"
|
|
echo " B4 — Installare act_runner come systemd service"
|
|
echo " B5 — Installare timer systemd da deploy/systemd/"
|
|
echo ""
|
|
echo "Vedi plans/PhaseB-user-checklist.md per i dettagli."
|