Left behind by a rename; broke the img2img (issue #121) crash-detection path. Typecheck back to 0 errors.
1109 lines
41 KiB
Bash
Executable File
1109 lines
41 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# pi-imgen — installazione per macOS su Apple Silicon.
|
|
#
|
|
# Ri-eseguibile per costruzione: ogni passo controlla prima di agire, ripara invece di
|
|
# duplicare, e NON sovrascrive mai i preset dell'utente in ~/.pi/agent/pi-imgen.json.
|
|
#
|
|
# NOTE FOR MAINTAINERS (English, per CODE STYLE; all user-visible strings are Italian):
|
|
# - macOS ships bash 3.2, so: no associative arrays, no ${var,,}, no `read -i`,
|
|
# and empty arrays must be guarded with ${#arr[@]} before expanding under `set -u`.
|
|
# - The gRPCServerCLI install location and pinned version MUST stay in sync with
|
|
# extensions/imgen/backends/server.ts (GRPC_SERVER_VERSION / serverPaths()), which
|
|
# defaults the binary to <piConfigDir>/imgen/gRPCServerCLI-macOS. We deliberately do
|
|
# NOT write into a Homebrew prefix we do not own, and we deliberately leave
|
|
# `server.binary` unset in the config so the code default keeps the two in step.
|
|
# - We never invent CLI flags. `draw-things-cli models ensure` is attempted ONLY if the
|
|
# locally installed CLI's own --help confirms both the subcommand and the flags; see
|
|
# prefetch_models(). Otherwise we print exact manual instructions and say so.
|
|
# - No sudo is ever run by this script. The only privileged step is the official
|
|
# Homebrew installer, which asks for its own password and only after we ask first.
|
|
|
|
set -euo pipefail
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Costanti
|
|
# ---------------------------------------------------------------------------
|
|
|
|
REPO_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
|
|
# Sincronizzati con extensions/imgen/backends/server.ts
|
|
GRPC_SERVER_VERSION="v1.20260716.0"
|
|
GRPC_SERVER_BINARY="gRPCServerCLI-macOS"
|
|
GRPC_SERVER_URL="https://github.com/drawthingsai/draw-things-community/releases/download/${GRPC_SERVER_VERSION}/${GRPC_SERVER_BINARY}"
|
|
|
|
MODELS_CATALOGUE_URL="https://models.drawthings.ai/models.json"
|
|
|
|
PI_CONFIG_DIR="${PI_CONFIG_DIR:-$HOME/.pi/agent}"
|
|
STATE_DIR="$PI_CONFIG_DIR/imgen"
|
|
CFG_PATH="$PI_CONFIG_DIR/pi-imgen.json"
|
|
|
|
BREW_REQUIRED="draw-things-cli typst"
|
|
BREW_OPTIONAL="ghostscript poppler qpdf"
|
|
|
|
# Spazio consigliato per i modelli (~10 GB di pesi + margine di lavoro).
|
|
MIN_MODELS_GB=15
|
|
|
|
DEFAULT_OUT_DIR="$HOME/Immagini/generated"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Interfaccia
|
|
# ---------------------------------------------------------------------------
|
|
|
|
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
|
|
C_RESET=$'\033[0m'; C_BOLD=$'\033[1m'; C_DIM=$'\033[2m'
|
|
C_RED=$'\033[31m'; C_GREEN=$'\033[32m'; C_YELLOW=$'\033[33m'; C_BLUE=$'\033[34m'
|
|
else
|
|
C_RESET=""; C_BOLD=""; C_DIM=""; C_RED=""; C_GREEN=""; C_YELLOW=""; C_BLUE=""
|
|
fi
|
|
|
|
STEP_NO=0
|
|
step() { STEP_NO=$((STEP_NO + 1)); printf '\n%s[%d/8] %s%s\n' "$C_BOLD$C_BLUE" "$STEP_NO" "$1" "$C_RESET"; }
|
|
ok() { printf ' %s✓%s %s\n' "$C_GREEN" "$C_RESET" "$1"; }
|
|
info() { printf ' %s·%s %s\n' "$C_DIM" "$C_RESET" "$1"; }
|
|
warn() { printf ' %s!%s %s\n' "$C_YELLOW" "$C_RESET" "$1"; }
|
|
err() { printf ' %s✗%s %s\n' "$C_RED" "$C_RESET" "$1" >&2; }
|
|
hint() { printf ' %s%s%s\n' "$C_DIM" "$1" "$C_RESET"; }
|
|
|
|
die() {
|
|
printf '\n%s✗ Installazione interrotta.%s %s\n\n' "$C_RED$C_BOLD" "$C_RESET" "$1" >&2
|
|
exit 1
|
|
}
|
|
|
|
# Esiti raccolti per il riepilogo finale.
|
|
SUMMARY_LINES=()
|
|
ESSENTIAL_MISSING=0
|
|
|
|
record() { # record <stato: ok|warn|fail> <voce> <dettaglio>
|
|
SUMMARY_LINES+=("$1"$'\t'"$2"$'\t'"$3")
|
|
[ "$1" = "fail" ] && ESSENTIAL_MISSING=$((ESSENTIAL_MISSING + 1))
|
|
return 0
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Opzioni
|
|
# ---------------------------------------------------------------------------
|
|
|
|
ASSUME_YES=0
|
|
DOCTOR_ONLY=0
|
|
SKIP_MODELS=0
|
|
ARG_MODELS_DIR=""
|
|
ARG_OUT_DIR=""
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
pi-imgen — installazione (macOS, Apple Silicon)
|
|
|
|
Uso: ./install.sh [opzioni]
|
|
|
|
-y, --yes non fare domande: usa i valori predefiniti (o quelli già
|
|
presenti nella configurazione). Utile per riesecuzioni.
|
|
--models-dir DIR cartella dei modelli (deve stare su un disco secondario)
|
|
--out-dir DIR cartella dei file generati
|
|
--skip-models non prescaricare i modelli
|
|
--doctor esegui solo il controllo finale, senza installare nulla
|
|
-h, --help mostra questo messaggio
|
|
|
|
Lo script è ri-eseguibile: ripara ciò che manca e non tocca i tuoi preset.
|
|
USAGE
|
|
}
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
-y|--yes) ASSUME_YES=1 ;;
|
|
--doctor) DOCTOR_ONLY=1 ;;
|
|
--skip-models) SKIP_MODELS=1 ;;
|
|
--models-dir) [ $# -ge 2 ] || die "Manca il valore per --models-dir."; ARG_MODELS_DIR="$2"; shift ;;
|
|
--models-dir=*) ARG_MODELS_DIR="${1#*=}" ;;
|
|
--out-dir) [ $# -ge 2 ] || die "Manca il valore per --out-dir."; ARG_OUT_DIR="$2"; shift ;;
|
|
--out-dir=*) ARG_OUT_DIR="${1#*=}" ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
*) usage >&2; die "Opzione sconosciuta: $1" ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
INTERACTIVE=1
|
|
if [ ! -t 0 ] || [ "$ASSUME_YES" = "1" ]; then INTERACTIVE=0; fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Utilità
|
|
# ---------------------------------------------------------------------------
|
|
|
|
have() { command -v "$1" >/dev/null 2>&1; }
|
|
|
|
expand_tilde() { # stampa il percorso con ~ espansa
|
|
case "$1" in
|
|
"~") printf '%s' "$HOME" ;;
|
|
"~/"*) printf '%s/%s' "$HOME" "${1#\~/}" ;;
|
|
*) printf '%s' "$1" ;;
|
|
esac
|
|
}
|
|
|
|
ask() { # ask <domanda> <predefinito> -> stampa la risposta
|
|
local question="$1" default="$2" reply=""
|
|
if [ "$INTERACTIVE" = "0" ]; then printf '%s' "$default"; return 0; fi
|
|
printf ' %s%s%s\n [%s]: ' "$C_BOLD" "$question" "$C_RESET" "$default" >&2
|
|
IFS= read -r reply || reply=""
|
|
[ -n "$reply" ] || reply="$default"
|
|
printf '%s' "$reply"
|
|
}
|
|
|
|
confirm() { # confirm <domanda> <s|n predefinito> -> 0 = sì
|
|
local question="$1" default="$2" reply=""
|
|
if [ "$INTERACTIVE" = "0" ]; then [ "$default" = "s" ]; return; fi
|
|
local prompt="s/N"; [ "$default" = "s" ] && prompt="S/n"
|
|
printf ' %s%s%s [%s]: ' "$C_BOLD" "$question" "$C_RESET" "$prompt" >&2
|
|
IFS= read -r reply || reply=""
|
|
[ -n "$reply" ] || reply="$default"
|
|
case "$reply" in [sSyY]*) return 0 ;; *) return 1 ;; esac
|
|
}
|
|
|
|
free_gb() { # spazio libero in GB; stampa sempre un numero, 0 se non si sa
|
|
local gb
|
|
gb="$(df -Pk "$1" 2>/dev/null | awk 'NR==2 { printf "%.0f", $4 / 1048576 }')"
|
|
case "$gb" in
|
|
''|*[!0-9]*) printf '0' ;;
|
|
*) printf '%s' "$gb" ;;
|
|
esac
|
|
}
|
|
|
|
# Un file scaricato è davvero un eseguibile macOS? Stessi magic number di server.ts:
|
|
# difende da una pagina di errore HTML o da un puntatore Git LFS serviti con 200 OK.
|
|
is_macho() {
|
|
local magic
|
|
magic="$(head -c 4 "$1" 2>/dev/null | od -An -tx1 | tr -d ' \n')"
|
|
case "$magic" in
|
|
feedfacf|cffaedfe|feedface|cefaedfe|cafebabe|bebafeca) return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
# Percorso scrivibile? Prova davvero a scrivere: -w mente su volumi di rete e su ACL.
|
|
dir_is_writable() {
|
|
local probe="$1/.pi-imgen-write-test.$$"
|
|
if ( umask 077; : > "$probe" ) 2>/dev/null; then rm -f "$probe"; return 0; fi
|
|
return 1
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 0. Configurazione esistente (letta prima di tutto: guida i valori predefiniti)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
EXIST_CFG_VALID=0
|
|
EXIST_MODELS_PATH=""
|
|
EXIST_OUT_DIR=""
|
|
EXIST_SERVER_BINARY=""
|
|
|
|
read_existing_config() {
|
|
[ -f "$CFG_PATH" ] || return 0
|
|
have python3 || return 0
|
|
local out=""
|
|
out="$(CFG_PATH="$CFG_PATH" python3 <<'PY' 2>/dev/null || true
|
|
import json, os, shlex
|
|
|
|
def emit(name, value):
|
|
print("%s=%s" % (name, shlex.quote(value if isinstance(value, str) else "")))
|
|
|
|
try:
|
|
with open(os.environ["CFG_PATH"], encoding="utf-8") as fh:
|
|
cfg = json.load(fh)
|
|
if not isinstance(cfg, dict):
|
|
raise ValueError("root is not an object")
|
|
except Exception:
|
|
raise SystemExit(0)
|
|
|
|
print("EXIST_CFG_VALID=1")
|
|
emit("EXIST_MODELS_PATH", cfg.get("modelsPath", ""))
|
|
emit("EXIST_OUT_DIR", cfg.get("outputDir", ""))
|
|
server = cfg.get("server") if isinstance(cfg.get("server"), dict) else {}
|
|
emit("EXIST_SERVER_BINARY", server.get("binary", ""))
|
|
PY
|
|
)"
|
|
# `out` contiene solo assegnazioni con valori già quotati da shlex.quote().
|
|
[ -n "$out" ] && eval "$out"
|
|
return 0
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 1. Sistema
|
|
# ---------------------------------------------------------------------------
|
|
|
|
check_system() {
|
|
step "Controllo del sistema"
|
|
|
|
local os arch
|
|
os="$(uname -s)"
|
|
arch="$(uname -m)"
|
|
|
|
if [ "$os" != "Darwin" ]; then
|
|
die "pi-imgen funziona solo su macOS: qui il sistema è «$os».
|
|
Il motore di generazione (Draw Things) esiste soltanto per macOS su Apple Silicon."
|
|
fi
|
|
|
|
# Sotto Rosetta `uname -m` risponde x86_64 anche su un Mac Apple Silicon: distinguere
|
|
# i due casi evita di dire a un utente M4 che il suo Mac non è compatibile.
|
|
local translated=0
|
|
translated="$(sysctl -n sysctl.proc_translated 2>/dev/null || printf '0')"
|
|
if [ "$translated" = "1" ]; then
|
|
die "Questo terminale sta girando sotto Rosetta (emulazione Intel).
|
|
Draw Things richiede un processo nativo arm64.
|
|
Apri un terminale nativo, oppure rilancia così:
|
|
arch -arm64 /bin/bash \"$REPO_DIR/install.sh\""
|
|
fi
|
|
|
|
if [ "$arch" != "arm64" ]; then
|
|
die "pi-imgen richiede un Mac Apple Silicon (M1 o successivi): qui l'architettura è «$arch».
|
|
Sui Mac Intel il motore di generazione non è disponibile e non esiste un ripiego."
|
|
fi
|
|
|
|
local osver; osver="$(sw_vers -productVersion 2>/dev/null || printf 'sconosciuta')"
|
|
ok "macOS $osver su Apple Silicon ($arch)."
|
|
|
|
# draw-things-cli richiede macOS 13+.
|
|
case "$osver" in
|
|
1[0-2].*|[0-9].*) warn "draw-things-cli richiede macOS 13 o successivo: questa è la $osver." ;;
|
|
esac
|
|
|
|
local membytes memgb
|
|
membytes="$(sysctl -n hw.memsize 2>/dev/null || printf '0')"
|
|
case "$membytes" in ''|*[!0-9]*) membytes=0 ;; esac
|
|
memgb="$(( membytes / 1073741824 ))"
|
|
if [ "$memgb" -gt 0 ]; then
|
|
info "Memoria unificata: ${memgb} GB."
|
|
if [ "$memgb" -le 16 ]; then
|
|
info "Con 16 GB il decoding a riquadri (tiled) resta attivo: dimezza la memoria di picco."
|
|
fi
|
|
fi
|
|
|
|
have python3 || die "Serve python3 (incluso in macOS) per aggiornare la configurazione senza perdere i tuoi preset."
|
|
have curl || die "Serve curl (incluso in macOS) per scaricare il server."
|
|
record ok "Sistema" "macOS $osver, Apple Silicon"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 2. Homebrew e pacchetti
|
|
# ---------------------------------------------------------------------------
|
|
|
|
BREW=""
|
|
|
|
find_brew() {
|
|
if have brew; then BREW="$(command -v brew)"; return 0; fi
|
|
if [ -x /opt/homebrew/bin/brew ]; then BREW=/opt/homebrew/bin/brew; return 0; fi
|
|
if [ -x /usr/local/bin/brew ]; then BREW=/usr/local/bin/brew; return 0; fi
|
|
return 1
|
|
}
|
|
|
|
ensure_homebrew() {
|
|
step "Homebrew"
|
|
|
|
if find_brew; then
|
|
ok "Homebrew trovato: $BREW"
|
|
else
|
|
warn "Homebrew non è installato: serve per draw-things-cli e typst."
|
|
hint "L'installazione ufficiale chiederà LA TUA PASSWORD (usa sudo per conto suo)."
|
|
if ! confirm "Installo Homebrew adesso?" "s"; then
|
|
record fail "Homebrew" "assente — installalo da https://brew.sh e rilancia"
|
|
err "Salto Homebrew. Senza, draw-things-cli e typst non si possono installare."
|
|
return 0
|
|
fi
|
|
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" \
|
|
|| die "Installazione di Homebrew fallita. Installalo a mano da https://brew.sh e rilancia questo script."
|
|
find_brew || die "Homebrew risulta installato ma non lo trovo nel PATH. Apri un nuovo terminale e rilancia."
|
|
ok "Homebrew installato: $BREW"
|
|
fi
|
|
|
|
# Su Apple Silicon il prefisso nativo è /opt/homebrew. Un brew in /usr/local è la
|
|
# versione Intel: funziona sotto Rosetta ma non fornirà bottiglie arm64.
|
|
case "$BREW" in
|
|
/usr/local/*) warn "Questo Homebrew sta in /usr/local: è l'installazione Intel. Per pacchetti arm64 serve quella in /opt/homebrew." ;;
|
|
esac
|
|
|
|
eval "$("$BREW" shellenv)" 2>/dev/null || true
|
|
|
|
local f
|
|
for f in $BREW_REQUIRED; do
|
|
if "$BREW" list --formula --versions "$f" >/dev/null 2>&1; then
|
|
ok "$f già installato ($("$BREW" list --formula --versions "$f" | head -1))."
|
|
record ok "$f" "già presente"
|
|
else
|
|
info "Installo $f…"
|
|
# NB: formula di Homebrew CORE. Niente tap personalizzati, niente --HEAD.
|
|
if "$BREW" install "$f"; then
|
|
ok "$f installato."
|
|
record ok "$f" "installato ora"
|
|
else
|
|
err "Installazione di $f fallita."
|
|
record fail "$f" "installazione fallita — riprova con: brew install $f"
|
|
fi
|
|
fi
|
|
done
|
|
|
|
# Facoltativi: servono solo al controllo pre-stampa del PDF.
|
|
local missing_opt=""
|
|
for f in $BREW_OPTIONAL; do
|
|
"$BREW" list --formula --versions "$f" >/dev/null 2>&1 || missing_opt="$missing_opt $f"
|
|
done
|
|
missing_opt="${missing_opt# }"
|
|
|
|
if [ -n "$missing_opt" ]; then
|
|
info "Facoltativi per il controllo pre-stampa (verifica di TrimBox, abbondanza e caratteri incorporati): $missing_opt"
|
|
hint "Senza, la locandina si genera lo stesso: salta solo la verifica del PDF."
|
|
if confirm "Li installo?" "n"; then
|
|
# shellcheck disable=SC2086
|
|
if "$BREW" install $missing_opt; then
|
|
ok "Strumenti di pre-stampa installati."
|
|
record ok "Pre-stampa" "$missing_opt installati"
|
|
else
|
|
warn "Installazione dei facoltativi fallita: si prosegue senza."
|
|
record warn "Pre-stampa" "installazione fallita (facoltativo)"
|
|
fi
|
|
else
|
|
info "Salto i facoltativi. Più avanti: brew install $missing_opt"
|
|
record warn "Pre-stampa" "assenti (facoltativo): $missing_opt"
|
|
fi
|
|
else
|
|
ok "Strumenti di pre-stampa già presenti ($BREW_OPTIONAL)."
|
|
record ok "Pre-stampa" "completi"
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 3. gRPCServerCLI (asset di release GitHub, NON in Homebrew)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
SERVER_TARGET=""
|
|
|
|
ensure_grpc_server() {
|
|
step "Server residente ($GRPC_SERVER_BINARY)"
|
|
|
|
# Se la configurazione indica già un percorso per il binario, rispettiamolo: è quello
|
|
# che il codice userà (config.server.binary vince su serverPaths()).
|
|
if [ -n "$EXIST_SERVER_BINARY" ]; then
|
|
SERVER_TARGET="$(expand_tilde "$EXIST_SERVER_BINARY")"
|
|
info "Percorso preso dalla configurazione esistente: $SERVER_TARGET"
|
|
else
|
|
SERVER_TARGET="$STATE_DIR/$GRPC_SERVER_BINARY"
|
|
fi
|
|
|
|
mkdir -p "$(dirname -- "$SERVER_TARGET")"
|
|
local stamp="$SERVER_TARGET.version"
|
|
|
|
info "Non è distribuito da Homebrew: brew install draw-things-cli dà solo il client."
|
|
|
|
if [ -f "$SERVER_TARGET" ]; then
|
|
if [ ! -x "$SERVER_TARGET" ]; then
|
|
chmod +x "$SERVER_TARGET" 2>/dev/null || true
|
|
info "Permesso di esecuzione ripristinato."
|
|
fi
|
|
if [ -x "$SERVER_TARGET" ] && is_macho "$SERVER_TARGET"; then
|
|
local installed=""
|
|
[ -f "$stamp" ] && installed="$(cat "$stamp" 2>/dev/null || printf '')"
|
|
|
|
if [ "$installed" = "$GRPC_SERVER_VERSION" ]; then
|
|
ok "Già presente e aggiornato ($GRPC_SERVER_VERSION): $SERVER_TARGET"
|
|
record ok "Server gRPC" "$GRPC_SERVER_VERSION"
|
|
return 0
|
|
fi
|
|
|
|
if [ -z "$installed" ]; then
|
|
# Installato a mano: è valido, quindi lo teniamo. Riscaricarlo a ogni
|
|
# esecuzione sarebbe uno spreco, e questo script deve essere ri-eseguibile.
|
|
ok "Già presente (installato a mano): $SERVER_TARGET"
|
|
info "Versione non determinabile; quella attesa è $GRPC_SERVER_VERSION."
|
|
hint "Per allinearlo: rm \"$SERVER_TARGET\" e rilancia ./install.sh"
|
|
record warn "Server gRPC" "presente, versione ignota (attesa $GRPC_SERVER_VERSION)"
|
|
return 0
|
|
fi
|
|
|
|
warn "Presente una versione diversa (installata: $installed, attesa: $GRPC_SERVER_VERSION)."
|
|
if ! confirm "Lo aggiorno?" "s"; then
|
|
record warn "Server gRPC" "versione $installed (attesa $GRPC_SERVER_VERSION)"
|
|
return 0
|
|
fi
|
|
else
|
|
warn "Il file esistente non è un eseguibile macOS valido: lo riscarico."
|
|
fi
|
|
fi
|
|
|
|
if ! confirm "Scarico $GRPC_SERVER_BINARY $GRPC_SERVER_VERSION (~decine di MB) da GitHub?" "s"; then
|
|
record fail "Server gRPC" "assente — scaricalo da $GRPC_SERVER_URL in $SERVER_TARGET"
|
|
warn "Salto lo scaricamento. Senza il server ogni immagine ricarica il modello da zero."
|
|
return 0
|
|
fi
|
|
|
|
local tmp="$SERVER_TARGET.part.$$"
|
|
info "Scarico da $GRPC_SERVER_URL"
|
|
if ! curl -fL --progress-bar -o "$tmp" "$GRPC_SERVER_URL"; then
|
|
rm -f "$tmp"
|
|
err "Scaricamento fallito (rete o URL non raggiungibile)."
|
|
hint "A mano: curl -fL -o \"$SERVER_TARGET\" \"$GRPC_SERVER_URL\" && chmod +x \"$SERVER_TARGET\""
|
|
record fail "Server gRPC" "scaricamento fallito"
|
|
return 0
|
|
fi
|
|
|
|
# Un 200 OK non garantisce un binario: verifichiamo dimensione e magic number.
|
|
local size; size="$(wc -c < "$tmp" | tr -d ' ')"
|
|
if [ "$size" -lt 1048576 ] || ! is_macho "$tmp"; then
|
|
rm -f "$tmp"
|
|
err "Il file scaricato non è un eseguibile macOS valido ($size byte): probabilmente una pagina di errore."
|
|
record fail "Server gRPC" "file scaricato non valido"
|
|
return 0
|
|
fi
|
|
|
|
chmod 755 "$tmp"
|
|
# Gatekeeper mette in quarantena i file scaricati: senza questo il primo avvio viene bloccato.
|
|
/usr/bin/xattr -d com.apple.quarantine "$tmp" 2>/dev/null || true
|
|
mv -f "$tmp" "$SERVER_TARGET"
|
|
printf '%s\n' "$GRPC_SERVER_VERSION" > "$stamp"
|
|
|
|
ok "Installato in $SERVER_TARGET ($GRPC_SERVER_VERSION)."
|
|
record ok "Server gRPC" "$GRPC_SERVER_VERSION"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 4. Percorsi: modelli (disco secondario) e destinazione
|
|
# ---------------------------------------------------------------------------
|
|
|
|
MODELS_PATH=""
|
|
OUT_DIR=""
|
|
|
|
suggest_models_dir() {
|
|
# Candidati: volumi montati sotto /Volumes che NON sono il disco di avvio.
|
|
local root_dev v dev gb best_gb=0 best=""
|
|
root_dev="$(df -P / 2>/dev/null | awk 'NR==2 { print $1 }')"
|
|
|
|
for v in /Volumes/*; do
|
|
[ -d "$v" ] || continue
|
|
case "$v" in
|
|
*com.apple.TimeMachine*|*.timemachine*) continue ;;
|
|
esac
|
|
dev="$(df -P "$v" 2>/dev/null | awk 'NR==2 { print $1 }')"
|
|
if [ -z "$dev" ] || [ "$dev" = "$root_dev" ]; then continue; fi
|
|
dir_is_writable "$v" || continue
|
|
gb="$(free_gb "$v")"
|
|
[ -n "$gb" ] || gb=0
|
|
if [ "$gb" -gt "$best_gb" ]; then best_gb="$gb"; best="$v"; fi
|
|
done
|
|
|
|
if [ -n "$best" ]; then printf '%s/pi-imgen/models' "$best"; return 0; fi
|
|
# Nessun disco secondario montato: proponiamo comunque un percorso fuori dalla home
|
|
# se esiste, altrimenti la home (chiedendo conferma esplicita più avanti).
|
|
printf '%s' "$HOME/Documents/Models"
|
|
}
|
|
|
|
prompt_paths() {
|
|
step "Dove tenere i modelli e i file generati"
|
|
|
|
# -- modelli ---------------------------------------------------------------
|
|
local default_models=""
|
|
if [ -n "$ARG_MODELS_DIR" ]; then
|
|
default_models="$ARG_MODELS_DIR"
|
|
elif [ -n "$EXIST_MODELS_PATH" ]; then
|
|
default_models="$EXIST_MODELS_PATH"
|
|
info "Configurazione attuale: $EXIST_MODELS_PATH"
|
|
else
|
|
default_models="$(suggest_models_dir)"
|
|
fi
|
|
|
|
printf ' %sI modelli occupano circa 10 GB. Tienili su un disco secondario o esterno,%s\n' "$C_DIM" "$C_RESET"
|
|
printf ' %snon nella cartella utente: così i backup di casa restano leggeri.%s\n' "$C_DIM" "$C_RESET"
|
|
|
|
local candidates="" v
|
|
for v in /Volumes/*; do
|
|
[ -d "$v" ] || continue
|
|
case "$v" in *com.apple.TimeMachine*) continue ;; esac
|
|
candidates="$candidates $v ($(free_gb "$v") GB liberi)"
|
|
done
|
|
[ -n "$candidates" ] && info "Volumi montati:$candidates"
|
|
|
|
while :; do
|
|
local answer
|
|
answer="$(ask "Cartella dei modelli" "$default_models")"
|
|
answer="$(expand_tilde "$answer")"
|
|
|
|
case "$answer" in
|
|
/*) ;;
|
|
*) err "Il percorso deve essere assoluto (deve iniziare con «/»). Ricevuto: «$answer»"
|
|
if [ "$INTERACTIVE" = "0" ]; then die "Percorso dei modelli non assoluto: $answer"; fi
|
|
continue ;;
|
|
esac
|
|
|
|
case "$answer" in
|
|
"$HOME"|"$HOME"/*)
|
|
warn "«$answer» sta nella tua cartella utente: finirà nei backup e in iCloud Drive."
|
|
if [ "$INTERACTIVE" = "1" ] && ! confirm "Va bene lo stesso?" "n"; then
|
|
default_models="$(suggest_models_dir)"
|
|
continue
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
if ! mkdir -p "$answer" 2>/dev/null; then
|
|
err "Non riesco a creare «$answer» (permessi, o disco non montato)."
|
|
if [ "$INTERACTIVE" = "0" ]; then die "Cartella dei modelli non creabile: $answer"; fi
|
|
continue
|
|
fi
|
|
if ! dir_is_writable "$answer"; then
|
|
err "«$answer» non è scrivibile (volume in sola lettura?)."
|
|
if [ "$INTERACTIVE" = "0" ]; then die "Cartella dei modelli non scrivibile: $answer"; fi
|
|
continue
|
|
fi
|
|
|
|
local gb; gb="$(free_gb "$answer")"; [ -n "$gb" ] || gb=0
|
|
if [ "$gb" -lt "$MIN_MODELS_GB" ]; then
|
|
warn "Spazio libero: ${gb} GB. Ne servono almeno ${MIN_MODELS_GB} per i modelli."
|
|
if [ "$INTERACTIVE" = "1" ] && ! confirm "Proseguo comunque?" "n"; then continue; fi
|
|
else
|
|
info "Spazio libero: ${gb} GB."
|
|
fi
|
|
|
|
MODELS_PATH="$answer"
|
|
ok "Modelli: $MODELS_PATH"
|
|
break
|
|
done
|
|
|
|
# -- destinazione ----------------------------------------------------------
|
|
local default_out=""
|
|
if [ -n "$ARG_OUT_DIR" ]; then default_out="$ARG_OUT_DIR"
|
|
elif [ -n "$EXIST_OUT_DIR" ]; then default_out="$EXIST_OUT_DIR"
|
|
else default_out="$DEFAULT_OUT_DIR"; fi
|
|
|
|
while :; do
|
|
local answer
|
|
answer="$(ask "Cartella dei file generati" "$default_out")"
|
|
answer="$(expand_tilde "$answer")"
|
|
|
|
case "$answer" in
|
|
/*) ;;
|
|
*) err "Il percorso deve essere assoluto. Ricevuto: «$answer»"
|
|
if [ "$INTERACTIVE" = "0" ]; then die "Cartella di destinazione non assoluta: $answer"; fi
|
|
continue ;;
|
|
esac
|
|
|
|
if ! mkdir -p "$answer" 2>/dev/null || ! dir_is_writable "$answer"; then
|
|
err "«$answer» non è utilizzabile: non riesco a scriverci."
|
|
if [ "$INTERACTIVE" = "0" ]; then die "Cartella di destinazione non scrivibile: $answer"; fi
|
|
continue
|
|
fi
|
|
|
|
OUT_DIR="$answer"
|
|
ok "Destinazione: $OUT_DIR"
|
|
break
|
|
done
|
|
|
|
record ok "Cartelle" "modelli in $MODELS_PATH, uscite in $OUT_DIR"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 5. Caratteri
|
|
# ---------------------------------------------------------------------------
|
|
|
|
FONT_SCRIPT="$REPO_DIR/scripts/fetch-fonts.sh"
|
|
|
|
fetch_fonts() {
|
|
step "Caratteri tipografici (22 famiglie OFL/Apache)"
|
|
|
|
if [ ! -f "$FONT_SCRIPT" ]; then
|
|
warn "Script assente: $FONT_SCRIPT"
|
|
hint "Senza i caratteri, l'impaginazione con Typst non parte."
|
|
record fail "Caratteri" "scripts/fetch-fonts.sh non trovato"
|
|
return 0
|
|
fi
|
|
|
|
[ -x "$FONT_SCRIPT" ] || chmod +x "$FONT_SCRIPT" 2>/dev/null || true
|
|
|
|
info "Eseguo scripts/fetch-fonts.sh…"
|
|
# Fallire qui non deve interrompere l'installazione: i caratteri si riscaricano dopo.
|
|
if ( cd "$REPO_DIR" && /usr/bin/env bash "$FONT_SCRIPT" ); then
|
|
local n; n="$(find "$REPO_DIR/vendor/fonts" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l | tr -d ' ')"
|
|
ok "Caratteri pronti ($n famiglie in vendor/fonts)."
|
|
record ok "Caratteri" "$n famiglie"
|
|
else
|
|
err "Scaricamento dei caratteri fallito."
|
|
hint "Riprova più tardi con: \"$FONT_SCRIPT\""
|
|
record fail "Caratteri" "scaricamento fallito"
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 6. Modelli
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# ModelsConfig (extensions/imgen/config.ts) dichiara obbligatori SOLO draft e final:
|
|
# alt, edit e upscale sono `?` e la loro assenza non deve far fallire nulla.
|
|
MODEL_KEYS_REQUIRED="draft final"
|
|
MODEL_KEYS_OPTIONAL="alt edit upscale"
|
|
|
|
# Legge dalla configurazione scritta i modelli per le chiavi indicate, una riga
|
|
# "chiave<TAB>file" per ciascuna chiave davvero presente e valorizzata.
|
|
config_model_files() { # config_model_files <chiave>...
|
|
if [ ! -f "$CFG_PATH" ] || ! have python3; then
|
|
return 0
|
|
fi
|
|
CFG_PATH="$CFG_PATH" MODEL_KEYS="$*" python3 <<'PY' 2>/dev/null || true
|
|
import json, os
|
|
try:
|
|
with open(os.environ["CFG_PATH"], encoding="utf-8") as fh:
|
|
models = json.load(fh).get("models", {})
|
|
if isinstance(models, dict):
|
|
for key in os.environ["MODEL_KEYS"].split():
|
|
value = models.get(key)
|
|
if isinstance(value, str) and value:
|
|
print(f"{key}\t{value}")
|
|
except Exception:
|
|
pass
|
|
PY
|
|
}
|
|
|
|
# Ripiego per una singola chiave: il valore in DEFAULTS.models dentro config.ts,
|
|
# così l'elenco non viene duplicato qui dentro.
|
|
default_model_file() { # default_model_file <chiave>
|
|
sed -n "s/^[[:space:]]*$1:[[:space:]]*\"\([A-Za-z0-9_.-]*\.ckpt\)\".*/\1/p" \
|
|
"$REPO_DIR/extensions/imgen/config.ts" 2>/dev/null | head -n 1
|
|
}
|
|
|
|
# I file attesi per un gruppo di chiavi. L'unione è la stessa che fa loadConfig:
|
|
# il file dell'utente vince chiave per chiave, il resto arriva da DEFAULTS.
|
|
model_files() { # model_files <chiave>...
|
|
local pairs key value
|
|
pairs="$(config_model_files "$@")"
|
|
for key in "$@"; do
|
|
value="$(printf '%s\n' "$pairs" | awk -F'\t' -v k="$key" '$1 == k { print $2; exit }')"
|
|
if [ -z "$value" ]; then value="$(default_model_file "$key")"; fi
|
|
if [ -n "$value" ]; then printf '%s\n' "$value"; fi
|
|
done | sort -u
|
|
}
|
|
|
|
required_model_files() { model_files $MODEL_KEYS_REQUIRED; }
|
|
|
|
# I facoltativi già coperti dai necessari (per impostazione predefinita draft e
|
|
# final sono lo stesso file) non vanno riproposti come «mancanti».
|
|
optional_model_files() {
|
|
local required
|
|
required="$(required_model_files)"
|
|
# grep -F con più righe = un motivo per riga: scarta i doppioni dei necessari.
|
|
model_files $MODEL_KEYS_OPTIONAL | grep -vxF "$required" || true
|
|
}
|
|
|
|
# Avviso, mai errore: senza questi si genera lo stesso, si perdono solo lo stile
|
|
# alternativo, le modifiche (img2img) e l'ingrandimento.
|
|
report_optional_models() {
|
|
local m missing="" have_any=0
|
|
for m in $(optional_model_files); do
|
|
if [ -f "$MODELS_PATH/$m" ]; then
|
|
have_any=1
|
|
else
|
|
missing="$missing $m"
|
|
fi
|
|
done
|
|
missing="${missing# }"
|
|
|
|
if [ -z "$missing" ]; then
|
|
if [ "$have_any" = "1" ]; then
|
|
ok "Anche i modelli facoltativi sono presenti."
|
|
record ok "Modelli extra" "presenti"
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
info "Facoltativi non presenti (nessun problema): $missing"
|
|
hint "Servono solo per lo stile alternativo, le modifiche e l'ingrandimento."
|
|
hint "Puoi aggiungerli quando ti servono, dall'app Draw Things o dal catalogo."
|
|
record warn "Modelli extra" "facoltativi assenti: $missing"
|
|
}
|
|
|
|
prefetch_models() {
|
|
step "Modelli di generazione"
|
|
|
|
if [ "$SKIP_MODELS" = "1" ]; then
|
|
info "Salto lo scaricamento dei modelli (--skip-models)."
|
|
record warn "Modelli" "scaricamento saltato"
|
|
return 0
|
|
fi
|
|
|
|
local wanted present=0
|
|
wanted="$(required_model_files)"
|
|
if [ -z "$wanted" ]; then
|
|
warn "Non riesco a determinare quali modelli servono."
|
|
record warn "Modelli" "elenco non determinato"
|
|
return 0
|
|
fi
|
|
|
|
local m missing=""
|
|
for m in $wanted; do
|
|
if [ -f "$MODELS_PATH/$m" ]; then
|
|
ok "Già presente: $m"
|
|
present=$((present + 1))
|
|
else
|
|
missing="$missing $m"
|
|
fi
|
|
done
|
|
missing="${missing# }"
|
|
|
|
if [ -z "$missing" ]; then
|
|
ok "I modelli necessari sono già in $MODELS_PATH."
|
|
record ok "Modelli" "$present presenti"
|
|
report_optional_models
|
|
return 0
|
|
fi
|
|
|
|
info "Mancano: $missing"
|
|
|
|
if ! have draw-things-cli; then
|
|
warn "draw-things-cli non è installato: non posso prescaricare nulla."
|
|
document_model_download
|
|
record fail "Modelli" "mancanti: $missing"
|
|
report_optional_models
|
|
return 0
|
|
fi
|
|
|
|
# NON inventiamo flag. Interroghiamo l'aiuto del CLI installato e usiamo il
|
|
# sottocomando SOLO se l'aiuto stesso conferma sia «ensure» sia «--models-dir».
|
|
local root_help models_help=""
|
|
root_help="$(draw-things-cli --help 2>&1 || true)"
|
|
if printf '%s' "$root_help" | grep -qE '(^|[[:space:]])models([[:space:]]|$)'; then
|
|
models_help="$(draw-things-cli models --help 2>&1 || true)"
|
|
fi
|
|
|
|
if [ -n "$models_help" ] \
|
|
&& printf '%s' "$models_help" | grep -qE '(^|[[:space:]])ensure([[:space:]]|$)' \
|
|
&& printf '%s' "$models_help" | grep -q -- '--models-dir'; then
|
|
|
|
info "Il CLI dichiara «models ensure»: uso la forma verificata dal suo --help."
|
|
local still=""
|
|
for m in $missing; do
|
|
info "Scarico $m…"
|
|
# Un'uscita a 0 non basta: il file su disco è l'unica prova che serve.
|
|
draw-things-cli models ensure --models-dir "$MODELS_PATH" "$m" || true
|
|
if [ -s "$MODELS_PATH/$m" ]; then
|
|
ok "$m pronto."
|
|
else
|
|
warn "$m non è arrivato in $MODELS_PATH."
|
|
still="$still $m"
|
|
fi
|
|
done
|
|
still="${still# }"
|
|
if [ -z "$still" ]; then
|
|
record ok "Modelli" "tutti presenti"
|
|
else
|
|
document_model_download
|
|
record fail "Modelli" "mancanti: $still"
|
|
fi
|
|
report_optional_models
|
|
return 0
|
|
fi
|
|
|
|
# Il sottocomando non esiste (o ha un'altra forma) in questa versione del CLI.
|
|
warn "Questa versione di draw-things-cli non espone «models ensure» nel suo aiuto."
|
|
info "Non tiro a indovinare i parametri: qui sotto trovi le vie sicure."
|
|
document_model_download
|
|
record fail "Modelli" "mancanti: $missing"
|
|
report_optional_models
|
|
}
|
|
|
|
document_model_download() {
|
|
cat <<EOF
|
|
|
|
${C_BOLD}Come procurarsi i modelli (tre strade, tutte verificate)${C_RESET}
|
|
|
|
1) ${C_BOLD}Dall'app Draw Things${C_RESET} (la più semplice)
|
|
Apri Draw Things, e dal gestore modelli scarica:
|
|
• Z-Image Turbo — versione «8-bit S» (i8x): usa il Neural Engine dell'M4
|
|
e dimezza i tempi
|
|
• FLUX.2 klein 4B — per lo stile alternativo e le modifiche
|
|
Poi imposta in Draw Things la stessa cartella modelli: $MODELS_PATH
|
|
|
|
2) ${C_BOLD}Al primo utilizzo${C_RESET}
|
|
--model di draw-things-cli accetta anche un riferimento HuggingFace
|
|
(hf://autore/repo oppure l'URL), quindi il modello viene risolto da solo
|
|
alla prima generazione. Serve la rete, e la prima volta è lenta.
|
|
|
|
3) ${C_BOLD}Dal catalogo ufficiale${C_RESET}
|
|
$MODELS_CATALOGUE_URL elenca i modelli disponibili con i rispettivi file.
|
|
Scarica il .ckpt che ti serve e mettilo in: $MODELS_PATH
|
|
|
|
${C_DIM}Nota: i suffissi _i8x / _i6x («8-bit S» / «6-bit S») usano Int8 con le matmul
|
|
hardware del Neural Engine M4 e sono nettamente più veloci. Su questo Mac, preferiscili.${C_RESET}
|
|
|
|
EOF
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 7. Configurazione (unione, mai sovrascrittura)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
write_config() {
|
|
step "Configurazione ($CFG_PATH)"
|
|
|
|
mkdir -p "$PI_CONFIG_DIR" "$STATE_DIR"
|
|
|
|
if [ -f "$CFG_PATH" ] && [ "$EXIST_CFG_VALID" != "1" ]; then
|
|
local backup="$CFG_PATH.rotto-$(date +%Y%m%d%H%M%S)"
|
|
cp -p "$CFG_PATH" "$backup"
|
|
warn "Il file esistente non era JSON valido: l'ho messo da parte in $backup"
|
|
elif [ -f "$CFG_PATH" ]; then
|
|
cp -p "$CFG_PATH" "$CFG_PATH.bak"
|
|
info "Copia di sicurezza: $CFG_PATH.bak"
|
|
fi
|
|
|
|
# Unione conservativa: i valori già presenti VINCONO, tranne le due cartelle che
|
|
# l'utente ha appena confermato. `presets` e `defaultPreset` non si toccano mai.
|
|
# Scriviamo solo ciò che è specifico di questa macchina: config.ts fonde comunque
|
|
# il file sopra DEFAULTS, quindi un file parziale è del tutto legittimo e riduce
|
|
# il rischio che l'installer resti indietro rispetto al codice.
|
|
if CFG_PATH="$CFG_PATH" MODELS_PATH="$MODELS_PATH" OUT_DIR="$OUT_DIR" python3 <<'PY'
|
|
import json, os, tempfile
|
|
|
|
path = os.environ["CFG_PATH"]
|
|
|
|
try:
|
|
with open(path, encoding="utf-8") as fh:
|
|
cfg = json.load(fh)
|
|
if not isinstance(cfg, dict):
|
|
cfg = {}
|
|
except Exception:
|
|
cfg = {}
|
|
|
|
# Le due cartelle vengono dal prompt (il cui default era il valore attuale).
|
|
cfg["modelsPath"] = os.environ["MODELS_PATH"]
|
|
cfg["outputDir"] = os.environ["OUT_DIR"]
|
|
|
|
def fill(section, values):
|
|
"""Riempi solo le chiavi assenti: le scelte dell'utente restano intatte."""
|
|
current = cfg.get(section)
|
|
if not isinstance(current, dict):
|
|
current = {}
|
|
for key, value in values.items():
|
|
current.setdefault(key, value)
|
|
cfg[section] = current
|
|
|
|
# Leve di memoria, decisive su 16 GB: il decoding a riquadri porta il picco a 1024²
|
|
# da 14,03 GB a 7,18 GB. Il picco alla decodifica VAE — non il peso del modello — è
|
|
# ciò che fa fuori una macchina da 16 GB.
|
|
# ATTENZIONE (non verificato): l'unità di decodingTileWidth/Height è assunta in pixel.
|
|
fill("tiling", {
|
|
"tiledDecoding": True,
|
|
"decodingTileWidth": 512,
|
|
"decodingTileHeight": 512,
|
|
"decodingTileOverlap": 64,
|
|
})
|
|
|
|
# `binary` resta volutamente non impostato: server.ts lo deduce da serverPaths(), così
|
|
# installer e codice non possono divergere. Se l'utente l'ha impostato, lo lasciamo.
|
|
fill("server", {"enabled": True, "port": 7859, "cpuOffload": True})
|
|
|
|
cfg.setdefault("presets", {})
|
|
cfg.setdefault("defaultPreset", None)
|
|
|
|
# Scrittura atomica: un'interruzione non deve lasciare mezza configurazione.
|
|
directory = os.path.dirname(path) or "."
|
|
fd, tmp = tempfile.mkstemp(dir=directory, prefix=".pi-imgen.", suffix=".json")
|
|
try:
|
|
with os.fdopen(fd, "w", encoding="utf-8") as fh:
|
|
json.dump(cfg, fh, indent=2, ensure_ascii=False)
|
|
fh.write("\n")
|
|
os.replace(tmp, path)
|
|
except Exception:
|
|
os.path.exists(tmp) and os.unlink(tmp)
|
|
raise
|
|
PY
|
|
then
|
|
ok "Configurazione aggiornata."
|
|
local kept; kept="$(C="$CFG_PATH" python3 -c 'import json,os;print(len(json.load(open(os.environ["C"])).get("presets") or {}))' 2>/dev/null || printf '0')"
|
|
if [ "$kept" != "0" ]; then ok "Preset conservati: $kept."; else info "Nessun preset salvato (li creerai con /presets)."; fi
|
|
record ok "Configurazione" "$CFG_PATH"
|
|
else
|
|
err "Scrittura della configurazione fallita."
|
|
record fail "Configurazione" "scrittura fallita in $CFG_PATH"
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 8. Controllo finale
|
|
# ---------------------------------------------------------------------------
|
|
|
|
doctor() {
|
|
step "Controllo finale"
|
|
|
|
local v
|
|
|
|
# -- strumenti -------------------------------------------------------------
|
|
if have draw-things-cli; then
|
|
v="$(draw-things-cli --version 2>&1 | head -1 || true)"
|
|
[ -n "$v" ] || v="presente"
|
|
ok "draw-things-cli: $v"
|
|
else
|
|
err "draw-things-cli non trovato nel PATH."
|
|
hint "brew install draw-things-cli"
|
|
fi
|
|
|
|
if have typst; then
|
|
ok "typst: $(typst --version 2>&1 | head -1)"
|
|
else
|
|
err "typst non trovato nel PATH."
|
|
hint "brew install typst"
|
|
fi
|
|
|
|
if have node; then
|
|
local nodemajor; nodemajor="$(node -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo 0)"
|
|
if [ "$nodemajor" -ge 20 ] 2>/dev/null; then
|
|
ok "Node $(node -v)."
|
|
else
|
|
warn "Node $(node -v): pi-imgen richiede Node 20 o successivo."
|
|
fi
|
|
else
|
|
warn "Node non trovato: lo fornisce pi, di solito non è un problema."
|
|
fi
|
|
|
|
# -- server ----------------------------------------------------------------
|
|
if [ -n "$SERVER_TARGET" ] && [ -x "$SERVER_TARGET" ] && is_macho "$SERVER_TARGET"; then
|
|
ok "Server residente pronto: $SERVER_TARGET"
|
|
else
|
|
err "Server residente assente o non eseguibile."
|
|
hint "curl -fL -o \"${SERVER_TARGET:-$STATE_DIR/$GRPC_SERVER_BINARY}\" \"$GRPC_SERVER_URL\" && chmod +x \"${SERVER_TARGET:-$STATE_DIR/$GRPC_SERVER_BINARY}\""
|
|
fi
|
|
|
|
# Porta 7859: già occupata significa quasi sempre un server già avviato — bene così,
|
|
# pi-imgen adotta quello esistente invece di avviarne un secondo (due processi di
|
|
# diffusione saturano 16 GB prima di finire una decodifica).
|
|
local port=7859
|
|
if have python3; then
|
|
port="$(CFG_PATH="$CFG_PATH" python3 -c 'import json,os;print((json.load(open(os.environ["CFG_PATH"])).get("server") or {}).get("port",7859))' 2>/dev/null || printf '7859')"
|
|
fi
|
|
if have nc && nc -z 127.0.0.1 "$port" >/dev/null 2>&1; then
|
|
info "Porta $port: qualcosa è già in ascolto (server già avviato: verrà adottato)."
|
|
else
|
|
info "Porta $port libera: il server partirà all'avvio della sessione pi."
|
|
fi
|
|
|
|
# -- cartelle e modelli ----------------------------------------------------
|
|
if [ -d "$MODELS_PATH" ] && dir_is_writable "$MODELS_PATH"; then
|
|
ok "Cartella modelli: $MODELS_PATH ($(free_gb "$MODELS_PATH") GB liberi)."
|
|
else
|
|
err "Cartella modelli non utilizzabile: $MODELS_PATH"
|
|
fi
|
|
|
|
if [ -d "$OUT_DIR" ] && dir_is_writable "$OUT_DIR"; then
|
|
ok "Cartella di destinazione: $OUT_DIR"
|
|
else
|
|
err "Cartella di destinazione non utilizzabile: $OUT_DIR"
|
|
fi
|
|
|
|
# Necessari (draft, final) e facoltativi (alt, edit, upscale) contati a parte:
|
|
# i secondi non rendono l'installazione incompleta.
|
|
local m found=0 lost=0
|
|
for m in $(required_model_files); do
|
|
if [ -f "$MODELS_PATH/$m" ]; then found=$((found + 1)); else lost=$((lost + 1)); fi
|
|
done
|
|
if [ "$lost" = "0" ] && [ "$found" -gt 0 ]; then
|
|
ok "Modelli necessari: $found presenti."
|
|
else
|
|
warn "Modelli necessari: $found presenti, $lost mancanti."
|
|
fi
|
|
|
|
local extra_found=0 extra_lost=0
|
|
for m in $(optional_model_files); do
|
|
if [ -f "$MODELS_PATH/$m" ]; then
|
|
extra_found=$((extra_found + 1))
|
|
else
|
|
extra_lost=$((extra_lost + 1))
|
|
fi
|
|
done
|
|
if [ "$extra_lost" = "0" ]; then
|
|
if [ "$extra_found" -gt 0 ]; then ok "Modelli facoltativi: $extra_found presenti."; fi
|
|
else
|
|
info "Modelli facoltativi: $extra_found presenti, $extra_lost non scaricati (va bene così)."
|
|
fi
|
|
|
|
# -- caratteri -------------------------------------------------------------
|
|
local nf; nf="$(find "$REPO_DIR/vendor/fonts" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l | tr -d ' ')"
|
|
if [ "$nf" -ge 22 ]; then ok "Caratteri: $nf famiglie."
|
|
elif [ "$nf" -gt 0 ]; then warn "Caratteri: solo $nf famiglie su 22."
|
|
else err "Caratteri assenti: esegui \"$FONT_SCRIPT\"."
|
|
fi
|
|
|
|
# -- configurazione --------------------------------------------------------
|
|
if [ -f "$CFG_PATH" ] && C="$CFG_PATH" python3 -c 'import json,os;json.load(open(os.environ["C"]))' >/dev/null 2>&1; then
|
|
ok "Configurazione leggibile: $CFG_PATH"
|
|
else
|
|
err "Configurazione assente o illeggibile: $CFG_PATH"
|
|
fi
|
|
}
|
|
|
|
print_summary() {
|
|
printf '\n%s────────────────────────────────────────────────────────────%s\n' "$C_DIM" "$C_RESET"
|
|
printf '%sRiepilogo%s\n\n' "$C_BOLD" "$C_RESET"
|
|
|
|
local line status label detail mark color
|
|
if [ "${#SUMMARY_LINES[@]}" -gt 0 ]; then
|
|
for line in "${SUMMARY_LINES[@]}"; do
|
|
status="${line%%$'\t'*}"
|
|
label="$(printf '%s' "$line" | cut -f2)"
|
|
detail="$(printf '%s' "$line" | cut -f3)"
|
|
case "$status" in
|
|
ok) mark="pronto" ; color="$C_GREEN" ;;
|
|
warn) mark="parziale" ; color="$C_YELLOW" ;;
|
|
*) mark="da sistemare" ; color="$C_RED" ;;
|
|
esac
|
|
printf ' %s%-12s%s %-16s %s\n' "$color" "$mark" "$C_RESET" "$label" "$detail"
|
|
done
|
|
fi
|
|
|
|
printf '\n'
|
|
if [ "$ESSENTIAL_MISSING" = "0" ]; then
|
|
printf '%s✓ Tutto pronto.%s\n\n' "$C_GREEN$C_BOLD" "$C_RESET"
|
|
printf ' Apri pi e prova:\n'
|
|
printf ' %s/poster%s una locandina completa, dal titolo al PDF per la stampa\n' "$C_BOLD" "$C_RESET"
|
|
printf ' %s/presets%s salva i tuoi colori, caratteri e logo\n\n' "$C_BOLD" "$C_RESET"
|
|
else
|
|
if [ "$ESSENTIAL_MISSING" = "1" ]; then
|
|
printf '%s! Manca ancora una cosa.%s\n\n' "$C_YELLOW$C_BOLD" "$C_RESET"
|
|
else
|
|
printf '%s! Mancano ancora %d cose.%s\n\n' "$C_YELLOW$C_BOLD" "$ESSENTIAL_MISSING" "$C_RESET"
|
|
fi
|
|
printf ' Sistema le voci «da sistemare» qui sopra e rilancia:\n'
|
|
printf ' %s./install.sh%s (ripara ciò che manca, non tocca i tuoi preset)\n\n' "$C_BOLD" "$C_RESET"
|
|
fi
|
|
|
|
printf ' %sConfigurazione:%s %s\n' "$C_DIM" "$C_RESET" "$CFG_PATH"
|
|
printf ' %sModelli:%s %s\n' "$C_DIM" "$C_RESET" "${MODELS_PATH:-non impostata}"
|
|
printf ' %sDestinazione:%s %s\n\n' "$C_DIM" "$C_RESET" "${OUT_DIR:-non impostata}"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# main
|
|
# ---------------------------------------------------------------------------
|
|
|
|
main() {
|
|
printf '\n%spi-imgen — installazione%s\n' "$C_BOLD" "$C_RESET"
|
|
printf '%sLocandine, loghi e immagini per eventi. Tutto in locale.%s\n' "$C_DIM" "$C_RESET"
|
|
|
|
read_existing_config
|
|
|
|
if [ "$DOCTOR_ONLY" = "1" ]; then
|
|
MODELS_PATH="$(expand_tilde "${EXIST_MODELS_PATH:-$HOME/Documents/Models}")"
|
|
OUT_DIR="$(expand_tilde "${EXIST_OUT_DIR:-$DEFAULT_OUT_DIR}")"
|
|
SERVER_TARGET="${EXIST_SERVER_BINARY:-$STATE_DIR/$GRPC_SERVER_BINARY}"
|
|
SERVER_TARGET="$(expand_tilde "$SERVER_TARGET")"
|
|
STEP_NO=7
|
|
doctor
|
|
printf '\n'
|
|
exit 0
|
|
fi
|
|
|
|
check_system
|
|
ensure_homebrew
|
|
ensure_grpc_server
|
|
prompt_paths
|
|
fetch_fonts
|
|
prefetch_models
|
|
write_config
|
|
doctor
|
|
print_summary
|
|
|
|
[ "$ESSENTIAL_MISSING" = "0" ] || exit 1
|
|
}
|
|
|
|
main "$@"
|