Version 1.0.1 - add install.sh

This commit is contained in:
2026-06-03 09:09:46 +02:00
parent 81541a3af5
commit 75f6665252
5 changed files with 700 additions and 97 deletions
Executable
+428
View File
@@ -0,0 +1,428 @@
#!/usr/bin/env bash
set -euo pipefail
# ─────────────────────────────────────────────────────
# Glastree — Installer interattivo per amministratori
# Richiede: bash 4+, curl, git (opzionale), composer,
# php 8.3+, node 20+, npm 10+, mysql (opz.)
# ─────────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
CYAN='\033[0;36m'; NC='\033[0m'
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERR]${NC} $*"; }
fail() { error "$*"; exit 1; }
ask() {
local prompt="$1" var_name="$2" default="${3:-}"
local val
if [ -n "$default" ]; then
read -r -p "$(echo -e "${CYAN}?${NC} ${prompt} [${default}]: ")" val
val="${val:-$default}"
else
read -r -p "$(echo -e "${CYAN}?${NC} ${prompt}: ")" val
fi
printf -v "$var_name" '%s' "$val"
}
ask_required() {
local prompt="$1" var_name="$2"
local val
while true; do
read -r -p "$(echo -e "${CYAN}?${NC} ${prompt}: ")" val
[ -n "$val" ] && break
warn "Valore obbligatorio."
done
printf -v "$var_name" '%s' "$val"
}
confirm() {
local prompt="$1"
local resp
read -r -p "$(echo -e "${CYAN}?${NC} ${prompt} [S/n]: ")" resp
[[ "$resp" =~ ^[Ss]?$ ]]
}
check_cmds() {
local missing=()
for c in "$@"; do
command -v "$c" &>/dev/null || missing+=("$c")
done
[ ${#missing[@]} -gt 0 ] && fail "Comandi mancanti: ${missing[*]}"
}
print_banner() {
cat << 'EOF'
____ _ _ _
/ ___| | __ _ ___| |_ ___ _ __ ___ | |_ ___
| | _| |/ _` / __| __/ _ \ '__/ _ \ | __/ _ \
| |_| | | (_| \__ \ || __/ | | __/ | || __/
\____|_|\__,_|___/\__\___|_| \___| \__\___|
Installer interattivo per sysadmin
EOF
}
# ─────────────────────────────────────────────────────
# PREFLIGHT
# ─────────────────────────────────────────────────────
print_banner
# Directory dello script
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR"
# Rileva modalità
echo ""
echo "Seleziona la modalità di installazione:"
echo " 1) Fresh Install su Apache (LAMP tradizionale)"
echo " 2) Fresh Install via Docker"
echo " 3) Restore da Backup (Apache)"
ask "Scegli [1/2/3]" INSTALL_MODE "1"
case "$INSTALL_MODE" in
1) MODE="apache" ;;
2) MODE="docker" ;;
3) MODE="restore" ;;
*) fail "Scelta non valida: $INSTALL_MODE" ;;
esac
# ─────────────────────────────────────────────────────
# PARAMETRI COMUNI
# ─────────────────────────────────────────────────────
echo ""
echo "══════════════════════════════════════════"
echo " PARAMETRI GENERALI"
echo "══════════════════════════════════════════"
ask_required "Nome del sito/app (es. Glastree MyChurch)" APP_NAME
ask_required "Nome del primo utente amministratore" ADMIN_NAME
ask_required "Email dell'amministratore" ADMIN_EMAIL
ask_required "Password per l'amministratore" ADMIN_PASSWORD
ask_required "URL pubblico del sito (es. https://glastree.esempio.it)" APP_URL
# ─────────────────────────────────────────────────────
# APACHE
# ─────────────────────────────────────────────────────
if [ "$MODE" = "apache" ] || [ "$MODE" = "restore" ]; then
echo ""
echo "══════════════════════════════════════════"
echo " DATABASE"
echo "══════════════════════════════════════════"
ask "Tipo database" DB_TYPE "mysql" # mysql|sqlite
if [ "$DB_TYPE" = "mysql" ]; then
ask_required "Host database" DB_HOST
ask "Porta database" DB_PORT "3306"
ask_required "Nome database (verrà creato se non esiste)" DB_NAME
ask_required "Utente database" DB_USER
ask_required "Password database" DB_PASS
fi
fi
# ─────────────────────────────────────────────────────
# APACHE — FRESH INSTALL
# ─────────────────────────────────────────────────────
if [ "$MODE" = "apache" ]; then
echo ""
echo "══════════════════════════════════════════"
echo " PRE-FLIGHT CHECKS (Apache)"
echo "══════════════════════════════════════════"
check_cmds php composer node npm
PHP_VER=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')
info "PHP versione: $PHP_VER"
if php -r 'exit(version_compare(PHP_VERSION, "8.3.0") ? 0 : 1);' 2>/dev/null; then
fail "PHP >= 8.3 richiesto (trovato $PHP_VER)"
fi
for ext in pdo_mysql mbstring xml curl zip gd fileinfo; do
php -m | grep -qi "$ext" || warn "Estensione PHP '$ext' non trovata"
done
echo ""
echo "══════════════════════════════════════════"
echo " INSTALLAZIONE APACHE"
echo "══════════════════════════════════════════"
info "Percorso installazione: $SCRIPT_DIR"
# 1. Ambiente
[ ! -f .env ] && cp .env.example .env
"$SCRIPT_DIR"/artisan key:generate --force
# 2. Config .env
sed -i "s|APP_NAME=.*|APP_NAME=\"$APP_NAME\"|" .env
sed -i "s|APP_URL=.*|APP_URL=$APP_URL|" .env
sed -i "s|APP_ENV=.*|APP_ENV=production|" .env
sed -i "s|APP_DEBUG=.*|APP_DEBUG=false|" .env
if [ "$DB_TYPE" = "mysql" ]; then
sed -i "s/DB_CONNECTION=.*/DB_CONNECTION=mysql/" .env
sed -i "s/DB_HOST=.*/DB_HOST=$DB_HOST/" .env
sed -i "s/DB_PORT=.*/DB_PORT=$DB_PORT/" .env
sed -i "s/DB_DATABASE=.*/DB_DATABASE=$DB_NAME/" .env
sed -i "s/DB_USERNAME=.*/DB_USERNAME=$DB_USER/" .env
sed -i "s/DB_PASSWORD=.*/DB_PASSWORD=$DB_PASS/" .env
info "Attendo MySQL..."
php -r "
\$start = time();
while (time() - \$start < 60) {
try {
new PDO('mysql:host=$DB_HOST;port=$DB_PORT', '$DB_USER', '$DB_PASS');
exit(0);
} catch (\PDOException \$e) {
sleep(2);
}
}
exit(1);
" && ok "MySQL pronto" || warn "Timeout MySQL — continuo comunque"
php "$SCRIPT_DIR"/artisan config:clear 2>/dev/null || true
else
sed -i "s/DB_CONNECTION=.*/DB_CONNECTION=sqlite/" .env
touch database/database.sqlite
chmod 664 database/database.sqlite
fi
# 3. Composer
info "Installazione dipendenze Composer..."
composer install --no-dev --optimize-autoloader --no-scripts
# 4. Migration + seed
info "Esecuzione migration e seed..."
php "$SCRIPT_DIR"/artisan migrate --seed --force
# 5. Storage link
php "$SCRIPT_DIR"/artisan storage:link --force 2>/dev/null || true
# 6. Crea admin
info "Creazione utente amministratore..."
php "$SCRIPT_DIR"/artisan tinker --execute="
\$u = new \App\Models\User;
\$u->name = '$ADMIN_NAME';
\$u->email = '$ADMIN_EMAIL';
\$u->password = bcrypt('$ADMIN_PASSWORD');
\$u->save();
" 2>/dev/null
# 7. Asset
if command -v npm &>/dev/null; then
info "Compilazione asset frontend..."
npm ci --ignore-scripts 2>/dev/null && npm run build
else
warn "npm non trovato — asset non compilati (usa AdminLTE via CDN)"
fi
# 8. Permessi
chown -R www-data:www-data storage bootstrap/cache 2>/dev/null || warn "chown fallito — esegui manualmente: chown -R www-data:www-data storage bootstrap/cache"
chmod -R 775 storage bootstrap/cache
ok "Installazione Apache completata!"
echo ""
echo "────────────────────────────────────────────"
echo " URL: $APP_URL"
echo " Admin: $ADMIN_EMAIL"
echo " Cartella: $SCRIPT_DIR"
echo "────────────────────────────────────────────"
echo ""
warn "Configura Apache con il VirtualHost che trovi nella Guida → Installazione."
warn "Se hai appena creato il database MySQL, assicurati che l'utente abbia privilegi CREATE."
fi
# ─────────────────────────────────────────────────────
# DOCKER
# ─────────────────────────────────────────────────────
if [ "$MODE" = "docker" ]; then
echo ""
echo "══════════════════════════════════════════"
echo " PRE-FLIGHT CHECKS (Docker)"
echo "══════════════════════════════════════════"
check_cmds docker
echo ""
echo "══════════════════════════════════════════"
echo " CONFIGURAZIONE DOCKER"
echo "══════════════════════════════════════════"
ask "Tipo database per Docker" DOCKER_DB "sqlite"
ask "Porta host (es. 8080)" DOCKER_PORT "8080"
COMPOSE_FILE="docker-compose.yml"
[ "$DOCKER_DB" = "mysql" ] && COMPOSE_FILE="docker-compose.mysql.yml"
echo ""
echo "══════════════════════════════════════════"
echo " INSTALLAZIONE DOCKER"
echo "══════════════════════════════════════════"
info "Build immagine Docker..."
docker compose -f "$COMPOSE_FILE" build
# Crea un .env temporaneo per il docker-entrypoint
# Il container genera APP_KEY e fa migrate da solo
# Ma dobbiamo passare le variabili per admin e app name
cat > docker-compose.override.yml << EOF
services:
glastree:
environment:
- APP_NAME=$APP_NAME
- APP_URL=$APP_URL
- APP_ENV=production
- APP_DEBUG=false
EOF
# Se mysql, la configurazione DB è già nel compose
# Se sqlite, non serve override per DB
info "Avvio container..."
docker compose -f "$COMPOSE_FILE" -f docker-compose.override.yml up -d
echo ""
info "Attendo che il container sia pronto..."
CONTAINER_NAME=$(docker compose -f "$COMPOSE_FILE" ps -q glastree 2>/dev/null | head -1)
if [ -n "$CONTAINER_NAME" ]; then
# Attendi che Apache sia in ascolto
for i in $(seq 1 30); do
docker exec "$CONTAINER_NAME" curl -sf -o /dev/null http://localhost/ 2>/dev/null && break
sleep 2
done
# Crea utente admin dentro il container
info "Creazione utente amministratore nel container..."
docker exec -i "$CONTAINER_NAME" php artisan tinker --execute="
\App\Models\User::where('email', '$ADMIN_EMAIL')->delete();
\$u = new \App\Models\User;
\$u->name = '$ADMIN_NAME';
\$u->email = '$ADMIN_EMAIL';
\$u->password = bcrypt('$ADMIN_PASSWORD');
\$u->save();
" 2>/dev/null
fi
rm -f docker-compose.override.yml
ok "Installazione Docker completata!"
echo ""
echo "────────────────────────────────────────────"
echo " URL: http://localhost:${DOCKER_PORT}"
echo " Admin: $ADMIN_EMAIL"
echo " Container: $(docker compose -f "$COMPOSE_FILE" ps -q glastree 2>/dev/null | head -1)"
echo "────────────────────────────────────────────"
echo ""
warn "Per URL pubblici, imposta APP_URL nelle environment del container."
warn "Dati persistenti: volume 'glastree_storage' e 'glastree_db' (sqlite) o 'mysql_data'."
fi
# ─────────────────────────────────────────────────────
# RESTORE DA BACKUP (Apache)
# ─────────────────────────────────────────────────────
if [ "$MODE" = "restore" ]; then
echo ""
echo "══════════════════════════════════════════"
echo " PRE-FLIGHT CHECKS (Restore)"
echo "══════════════════════════════════════════"
check_cmds php composer unzip mysql
ask_required "Percorso del file ZIP di backup" BACKUP_ZIP
[ ! -f "$BACKUP_ZIP" ] && fail "File non trovato: $BACKUP_ZIP"
echo ""
echo "══════════════════════════════════════════"
echo " RESTORE DA BACKUP"
echo "══════════════════════════════════════════"
# Estrai backup in temp
TMP_DIR=$(mktemp -d)
info "Estrazione backup in $TMP_DIR ..."
unzip -q "$BACKUP_ZIP" -d "$TMP_DIR"
# 1. Ambiente
[ ! -f .env ] && cp .env.example .env
# 2. Ripristina .env dal backup (preservando DB creds)
if [ -f "$TMP_DIR/.env" ]; then
cp .env .env.backup-pre-restore
# Estrai DB_* dal .env corrente
for key in DB_CONNECTION DB_HOST DB_PORT DB_DATABASE DB_USERNAME DB_PASSWORD; do
current_val=$(grep "^${key}=" .env 2>/dev/null | cut -d= -f2-)
[ -n "$current_val" ] && export "CURRENT_${key}=$current_val"
done
cp "$TMP_DIR/.env" .env
# Ripristina le credenziali DB del nuovo server
for key in DB_CONNECTION DB_HOST DB_PORT DB_DATABASE DB_USERNAME DB_PASSWORD; do
var="CURRENT_${key}"
if [ -n "${!var:-}" ]; then
sed -i "s|^${key}=.*|${key}=${!var}|" .env
fi
done
ok ".env ripristinato dal backup (credenziali DB preservate)"
fi
sed -i "s|APP_NAME=.*|APP_NAME=\"$APP_NAME\"|" .env
sed -i "s|APP_URL=.*|APP_URL=$APP_URL|" .env
sed -i "s|APP_ENV=.*|APP_ENV=production|" .env
sed -i "s|APP_DEBUG=.*|APP_DEBUG=false|" .env
# 3. rigenera APP_KEY
"$SCRIPT_DIR"/artisan key:generate --force
# 4. Importa database
if [ "$DB_TYPE" = "mysql" ]; then
info "Importazione database MySQL..."
if [ -f "$TMP_DIR/database.sql" ]; then
mysql -h "$DB_HOST" -P "$DB_PORT" -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" < "$TMP_DIR/database.sql"
ok "Database importato"
else
warn "database.sql non trovato nel backup"
fi
else
warn "Tipo database non supportato per restore automatico (usa SQLite o importa manualmente)"
fi
# 5. Ripristina storage
if [ -d "$TMP_DIR/storage" ]; then
info "Ripristino file storage..."
cp -r "$TMP_DIR/storage"/* "$SCRIPT_DIR"/storage/ 2>/dev/null || true
ok "Storage ripristinato"
fi
# 6. Composer
composer install --no-dev --optimize-autoloader --no-scripts
# 7. Storage link + cache
php "$SCRIPT_DIR"/artisan storage:link --force 2>/dev/null || true
php "$SCRIPT_DIR"/artisan optimize:clear
# 8. Permessi
chown -R www-data:www-data storage bootstrap/cache 2>/dev/null || warn "chown fallito"
chmod -R 775 storage bootstrap/cache
# 9. Crea admin se non esiste
php "$SCRIPT_DIR"/artisan tinker --execute="
\$exists = \App\Models\User::where('email', '$ADMIN_EMAIL')->exists();
if (!\$exists) {
\$u = new \App\Models\User;
\$u->name = '$ADMIN_NAME';
\$u->email = '$ADMIN_EMAIL';
\$u->password = bcrypt('$ADMIN_PASSWORD');
\$u->save();
}
" 2>/dev/null
# Cleanup
rm -rf "$TMP_DIR"
ok "Restore completato!"
echo ""
echo "────────────────────────────────────────────"
echo " URL: $APP_URL"
echo " Admin: $ADMIN_EMAIL"
echo " Cartella: $SCRIPT_DIR"
echo "────────────────────────────────────────────"
echo ""
warn "Verifica che la APP_KEY non abbia invalidato dati criptati (token OAuth, password IMAP)."
warn "Se necessario, riconfigura SMTP e Google Drive dalle Impostazioni."
fi