@materializecms/cli 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,32 @@
1
+ # Novità di Materialize
2
+
3
+ Ogni versione, dalla più recente. Le novità in arrivo si scrivono sotto «Prossima versione»: `npm run bump` le assegna alla nuova versione.
4
+
5
+ ## Prossima versione
6
+
7
+ ## 0.2.0 — 2026-11-01
8
+
9
+ - **Password dimenticata?** nella pagina di accesso: un link per email, valido un'ora. Senza email configurata, il comando `materialize reset-password` stampa il link sul server, anche per l'unico amministratore.
10
+ - **Cronologia per le pagine sulla tela**: ogni versione si guarda su desktop, tablet e mobile prima di ripristinarla.
11
+ - **Esporta la pagina in PDF o PNG** dall'editor, per mandarla al cliente: desktop, tablet e mobile, ognuno come un'unica immagine lunga.
12
+ - **Guida per chi usa Materialize**, in `docs/guida`: installazione, editor, contenuti, lingue, SEO, persone, aggiornamenti, backup e problemi comuni.
13
+ - **Sicurezza**: i link degli inviti e delle nuove password puntano sempre all'indirizzo pubblico del sito, mai a quello dichiarato da chi fa la richiesta. Consigliato aggiornare.
14
+
15
+ ## 0.1.0 — 2026-10-31
16
+
17
+ La prima versione pubblica.
18
+
19
+ - **Editor visuale**: pagine disegnate su una tela, con sezioni, colonne, testi, immagini, video, icone e forme; componenti riutilizzabili con varianti; righelli, guide, allineamenti e misure; modalità Contenuti per chi modifica solo i testi.
20
+ - **Animazioni ed effetti**: animazioni allo scorrimento e con linee temporali, stati al passaggio del mouse, sfondi WebGL e oggetti 3D.
21
+ - **Contenuti**: raccolte come Progetti, Diario e Team, con pagine modello; azioni rapide e selezione multipla negli elenchi e nei Media.
22
+ - **Siti in più lingue**: traduzioni collegate tra loro, anche con l’AI, e un selettore di lingua per il sito.
23
+ - **SEO**: titoli e descrizioni, sitemap, controlli sulla pagina e reindirizzamenti automatici quando cambia un indirizzo.
24
+ - **Moduli di contatto** con invio delle email dal tuo server di posta.
25
+ - **Assistente AI e server MCP**: testi, traduzioni e modifiche alle pagine chiedendole a parole.
26
+ - **Importazione da WordPress**: pagine, articoli e immagini.
27
+ - **Statistiche e cookie**: Google Analytics, Tag Manager, Meta Pixel, Plausible, Matomo e codice personalizzato, con il banner per il consenso.
28
+ - **Installazione su VPS con un comando**: Node.js, HTTPS con Caddy, servizio che riparte da solo, firewall e link per finire nel browser.
29
+ - **Immagine Docker** con `compose.yaml` pronto per l'HTTPS: il sito sta in un volume, quindi gli aggiornamenti dal pannello restano anche ricreando il container.
30
+ - **Installazione nel browser**: controllo del server, nome e lingua del sito, account amministratore, passo per passo.
31
+ - **Aggiornamenti con un clic** da Impostazioni → Aggiornamenti, con backup prima e ritorno automatico alla versione precedente se la nuova non parte; le correzioni possono installarsi da sole.
32
+ - **Sicurezza**: verifica in due passaggi, pausa dopo i tentativi di accesso falliti, registro delle attività, backup giornalieri del database e dei media.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cristiano Casazza
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,21 @@
1
- # Temporary Holding Version
1
+ # @materializecms/cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Il comando materialize: sviluppo, produzione con aggiornamenti dal pannello, generazione del sito, backup, MCP.
4
+
5
+ ```bash
6
+ materialize dev # sviluppo, con lo schema ricaricato a ogni salvataggio
7
+ materialize start # produzione, con il supervisore che applica gli aggiornamenti
8
+ materialize build # il sito statico in dist/client
9
+ ```
10
+
11
+ Contiene anche `install.sh`, l'installazione su un VPS con un comando, e `compose.yaml` per Docker.
12
+
13
+ ## Materialize
14
+
15
+ CMS open source con editor visuale: le pagine si disegnano sulla tela, il sito è fatto di file statici veloci. Per un nuovo sito (serve Node 24):
16
+
17
+ ```bash
18
+ npm create materialize@latest
19
+ ```
20
+
21
+ Documentazione e codice: https://github.com/materializecms/materialize · Licenza MIT.
package/compose.yaml ADDED
@@ -0,0 +1,36 @@
1
+ # Materialize with HTTPS, on a server where Docker is already there:
2
+ #
3
+ # echo "DOMAIN=example.com" > .env (read by every docker compose command in this folder)
4
+ # docker compose up -d
5
+ # docker compose logs materialize (the link to finish the installation in the browser)
6
+ #
7
+ # Caddy gets the Let's Encrypt certificate for the domain, which must already point to the server.
8
+ # The site and its content stay in the "site" volume; updates are installed from the panel.
9
+
10
+ services:
11
+ materialize:
12
+ image: ${MATERIALIZE_IMAGE:-ghcr.io/materializecms/materialize:latest}
13
+ restart: unless-stopped
14
+ volumes:
15
+ - site:/site
16
+ environment:
17
+ MATERIALIZE_SITE_URL: https://${DOMAIN:?Indica il dominio nel file .env, per esempio DOMAIN=example.com}
18
+ MATERIALIZE_TRUST_PROXY: "1"
19
+ # Other variables for the site go here, for example ANTHROPIC_API_KEY.
20
+
21
+ caddy:
22
+ image: caddy:2
23
+ restart: unless-stopped
24
+ command: caddy reverse-proxy --from ${DOMAIN} --to materialize:4321
25
+ ports:
26
+ - "80:80"
27
+ - "443:443"
28
+ - "443:443/udp"
29
+ volumes:
30
+ - caddy:/data
31
+ depends_on:
32
+ - materialize
33
+
34
+ volumes:
35
+ site:
36
+ caddy:
package/install.sh ADDED
@@ -0,0 +1,437 @@
1
+ #!/usr/bin/env bash
2
+ # Materialize on a VPS, in one command (Debian 12+ or Ubuntu 22.04+, as root):
3
+ #
4
+ # curl -fsSL https://cdn.jsdelivr.net/npm/@materializecms/cli/install.sh | sudo bash -s -- --domain example.com
5
+ #
6
+ # Installs Node.js and Caddy (HTTPS with Let's Encrypt), creates the site in /srv/materialize, runs
7
+ # it as a service that starts with the server, opens only SSH, HTTP and HTTPS in the firewall and
8
+ # prints the link that finishes the installation in the browser. Running it again repairs the
9
+ # installation and prints the link again: the site and its content are never touched.
10
+ #
11
+ # Options:
12
+ # --domain <nome> The site's domain (asked when missing)
13
+ # --email <indirizzo> For Let's Encrypt's notices about the certificate (optional)
14
+ # --version <x.y.z> The Materialize version for a new site (default: the latest)
15
+ # --dir <cartella> Where the site lives (default: /srv/materialize)
16
+ # --port <numero> The local port behind Caddy (default: 4321)
17
+ # --registry <url> Another npm registry (a mirror, or a local one for trials)
18
+ # --tls internal A certificate from Caddy's own authority, to try it without a public domain
19
+ # --no-firewall Leaves the firewall as it is
20
+ # --yes Asks nothing
21
+
22
+ set -Eeuo pipefail
23
+
24
+ # Everything lives in main, run on the last line: a download cut halfway runs nothing.
25
+ main() {
26
+ DOMAIN='' EMAIL='' VERSION='latest' DIR='/srv/materialize' PORT='4321' REGISTRY='' TLS='' FIREWALL=1 YES=0
27
+ while [ $# -gt 0 ]; do
28
+ case "$1" in
29
+ --domain) DOMAIN="${2:-}"; shift 2 ;;
30
+ --email) EMAIL="${2:-}"; shift 2 ;;
31
+ --version) VERSION="${2:-}"; shift 2 ;;
32
+ --dir) DIR="${2:-}"; shift 2 ;;
33
+ --port) PORT="${2:-}"; shift 2 ;;
34
+ --registry) REGISTRY="${2:-}"; shift 2 ;;
35
+ --tls) TLS="${2:-}"; shift 2 ;;
36
+ --no-firewall) FIREWALL=0; shift ;;
37
+ --yes | -y) YES=1; shift ;;
38
+ -h | --help) usage; exit 0 ;;
39
+ *) die "Opzione sconosciuta: $1" ;;
40
+ esac
41
+ done
42
+
43
+ LOG=/var/log/materialize-install.log
44
+ USER_NAME=materialize
45
+ HOME_DIR=/var/lib/materialize
46
+ NODE_MAJOR=24
47
+
48
+ title
49
+ preflight
50
+ ask_domain
51
+ check_dns
52
+
53
+ : >"$LOG"
54
+ trap 'failed $LINENO' ERR
55
+ step 'Aggiorno l’elenco dei pacchetti'
56
+ apt_get update
57
+ apt_get install ca-certificates curl gnupg
58
+ add_swap
59
+ install_node
60
+ install_caddy
61
+ install_updates
62
+ create_user
63
+ create_site
64
+ write_service
65
+ write_caddy
66
+ open_firewall
67
+ start_site
68
+ finish
69
+ }
70
+
71
+ usage() {
72
+ cat <<'EOF'
73
+
74
+ curl -fsSL https://cdn.jsdelivr.net/npm/@materializecms/cli/install.sh | sudo bash -s -- --domain example.com
75
+
76
+ --domain <nome> Il dominio del sito (se manca, lo chiede)
77
+ --email <indirizzo> Per gli avvisi di Let's Encrypt sul certificato (facoltativo)
78
+ --version <x.y.z> La versione di Materialize per un sito nuovo (predefinita: l'ultima)
79
+ --dir <cartella> Dove sta il sito (predefinita: /srv/materialize)
80
+ --port <numero> La porta locale dietro Caddy (predefinita: 4321)
81
+ --registry <url> Un altro registro npm
82
+ --tls internal Un certificato di Caddy stesso, per provare senza un dominio pubblico
83
+ --no-firewall Lascia il firewall com'è
84
+ --yes Non chiede niente
85
+
86
+ EOF
87
+ }
88
+
89
+ # ── Output ────────────────────────────────────────────────────────────────────────────────
90
+
91
+ if [ -t 1 ]; then B=$'\e[1m' D=$'\e[2m' G=$'\e[32m' R=$'\e[31m' Y=$'\e[33m' C=$'\e[34m' N=$'\e[0m'; else B='' D='' G='' R='' Y='' C='' N=''; fi
92
+
93
+ title() { printf '\n %sMaterialize%s %sinstallazione su VPS%s\n\n' "$B$C" "$N" "$D" "$N"; }
94
+ step() { printf ' %s→%s %s…\n' "$C" "$N" "$1"; }
95
+ ok() { printf ' %s✓%s %s\n' "$G" "$N" "$1"; }
96
+ warn() { printf ' %s!%s %s\n' "$Y" "$N" "$1"; }
97
+ note() { printf ' %s%s%s\n' "$D" "$1" "$N"; }
98
+ die() { printf '\n %s✗ %s%s\n\n' "$R" "$1" "$N" >&2; exit 1; }
99
+
100
+ failed() {
101
+ printf '\n %s✗ Qualcosa non è andato (riga %s).%s Le ultime righe del registro:\n\n' "$R" "$1" "$N" >&2
102
+ tail -n 15 "$LOG" 2>/dev/null | sed 's/^/ /' >&2
103
+ printf '\n Il registro completo è in %s. Sistemato il problema, rilancia lo stesso comando:\n' "$LOG" >&2
104
+ printf ' riprende da dove si era fermato.\n\n' >&2
105
+ exit 1
106
+ }
107
+
108
+ # Long commands write to the log, not to the screen.
109
+ quiet() { "$@" >>"$LOG" 2>&1; }
110
+ apt_get() { DEBIAN_FRONTEND=noninteractive quiet apt-get -y -o Dpkg::Options::=--force-confold "$@"; }
111
+ # Commands of the site run as its own user, with its home for npm's cache.
112
+ as_site() { runuser -u "$USER_NAME" -- env HOME="$HOME_DIR" ${REGISTRY:+npm_config_registry="$REGISTRY"} "$@"; }
113
+
114
+ # Questions go to the terminal even when the script itself arrives from curl on stdin.
115
+ can_ask() { [ "$YES" = 0 ] && { : </dev/tty; } 2>/dev/null; }
116
+ ask() {
117
+ local answer
118
+ printf ' %s ' "$1" >/dev/tty
119
+ read -r answer </dev/tty || answer=''
120
+ printf '%s' "$answer"
121
+ }
122
+
123
+ # ── Checks ────────────────────────────────────────────────────────────────────────────────
124
+
125
+ preflight() {
126
+ [ "$(id -u)" = 0 ] || die 'Serve l’utente root: rilancia con sudo davanti a bash.'
127
+ [ -r /etc/os-release ] || die 'Sistema non riconosciuto: servono Debian 12 o Ubuntu 22.04 (o più recenti).'
128
+ # In a subshell: os-release has its own VERSION.
129
+ local ID VERSION_ID PRETTY_NAME
130
+ IFS='|' read -r ID VERSION_ID PRETTY_NAME < <(. /etc/os-release && echo "$ID|${VERSION_ID:-}|${PRETTY_NAME:-$ID}")
131
+ local major="${VERSION_ID%%.*}"
132
+ case "$ID" in
133
+ debian) [ "${major:-0}" -ge 12 ] || die "Debian $VERSION_ID è troppo vecchio: serve Debian 12 o più recente." ;;
134
+ ubuntu) [ "${major:-0}" -ge 22 ] || die "Ubuntu $VERSION_ID è troppo vecchio: serve Ubuntu 22.04 o più recente." ;;
135
+ *) die "${PRETTY_NAME:-Questo sistema} non è supportato: servono Debian 12 o Ubuntu 22.04 (o più recenti)." ;;
136
+ esac
137
+ [ -d /run/systemd/system ] || die 'Manca systemd, che tiene acceso il sito: serve un VPS vero, non un container.'
138
+ case "$(uname -m)" in x86_64 | aarch64) ;; *) die "Il processore $(uname -m) non è supportato: servono x86_64 o arm64." ;; esac
139
+ [[ "$PORT" =~ ^[0-9]+$ ]] || die "Porta non valida: $PORT"
140
+ if [ -d "$DIR" ] && [ ! -f "$DIR/materialize.config.ts" ] && [ ! -f "$DIR/materialize.config.js" ] && [ -n "$(ls -A "$DIR")" ]; then
141
+ die "$DIR esiste e contiene altri file: scegli un’altra cartella con --dir."
142
+ fi
143
+
144
+ local ram_mb disk_mb
145
+ ram_mb=$(awk '/MemTotal/ { print int($2 / 1024) }' /proc/meminfo)
146
+ mkdir -p "$(dirname "$DIR")"
147
+ disk_mb=$(df -Pm "$(dirname "$DIR")" | awk 'NR == 2 { print $4 }')
148
+ [ "$disk_mb" -ge 2048 ] || die "Servono almeno 2 GB liberi su disco, ce ne sono $((disk_mb / 1024))."
149
+ [ "$ram_mb" -ge 900 ] || die "Servono almeno 1 GB di memoria, il server ne ha ${ram_mb} MB."
150
+ ok "${PRETTY_NAME}, $((ram_mb + 50)) MB di memoria, $((disk_mb / 1024)) GB liberi"
151
+ }
152
+
153
+ ask_domain() {
154
+ if [ -z "$DOMAIN" ] && [ -f /etc/caddy/materialize.caddy ]; then
155
+ # Running again: the domain chosen the first time.
156
+ DOMAIN=$(sed -n 's/^# dominio: //p' /etc/caddy/materialize.caddy | head -n 1)
157
+ fi
158
+ if [ -z "$DOMAIN" ]; then
159
+ can_ask || die 'Indica il dominio del sito: --domain example.com'
160
+ DOMAIN=$(ask 'Il dominio del sito (per esempio example.com):')
161
+ fi
162
+ DOMAIN=$(printf '%s' "$DOMAIN" | tr 'A-Z' 'a-z' | sed -e 's#^https\{0,1\}://##' -e 's#/.*$##')
163
+ [[ "$DOMAIN" =~ ^([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z][a-z0-9-]*[a-z0-9]$ ]] || die "«$DOMAIN» non sembra un dominio."
164
+ }
165
+
166
+ # Let's Encrypt gives a certificate only to a domain that already points here.
167
+ check_dns() {
168
+ WWW=0
169
+ [ "$TLS" = internal ] && return
170
+ local addresses mine
171
+ addresses=$(getent ahosts "$DOMAIN" | awk '{ print $1 }' | sort -u | tr '\n' ' ')
172
+ mine=" $(hostname -I 2>/dev/null) $(curl -fsS4 --max-time 5 https://api.ipify.org 2>/dev/null || true) $(curl -fsS6 --max-time 5 https://api6.ipify.org 2>/dev/null || true) "
173
+ if [ -z "$addresses" ]; then
174
+ warn "$DOMAIN non punta ancora a nessun indirizzo."
175
+ note 'Nel pannello del dominio crea un record A con l’indirizzo IP di questo server.'
176
+ note 'Puoi continuare: il certificato HTTPS arriva da solo appena il dominio punta qui.'
177
+ else
178
+ local found=0 a
179
+ for a in $addresses; do case "$mine" in *" $a "*) found=1 ;; esac; done
180
+ if [ "$found" = 1 ]; then
181
+ ok "$DOMAIN punta a questo server"
182
+ else
183
+ warn "$DOMAIN punta a ${addresses% }, che non sembra questo server."
184
+ note 'Se usi Cloudflare con la nuvola arancione, mettila grigia finché il certificato non è pronto.'
185
+ note 'Altrimenti correggi il record A: il certificato HTTPS arriva da solo appena il dominio punta qui.'
186
+ fi
187
+ fi
188
+ # www.<domain> gets a redirect when it points here too.
189
+ if [ -n "$(getent ahosts "www.$DOMAIN" 2>/dev/null)" ]; then WWW=1; fi
190
+ if can_ask && [ -n "${addresses:-}" ] && [ "${found:-0}" = 0 ]; then
191
+ case "$(ask 'Continuo lo stesso? [S/n]')" in [nN]*) exit 1 ;; esac
192
+ fi
193
+ }
194
+
195
+ # ── Installation ──────────────────────────────────────────────────────────────────────────
196
+
197
+ # Building the site needs more than 1 GB: small servers get some swap.
198
+ add_swap() {
199
+ local ram_mb
200
+ ram_mb=$(awk '/MemTotal/ { print int($2 / 1024) }' /proc/meminfo)
201
+ if [ "$ram_mb" -lt 1900 ] && [ -z "$(swapon --show --noheadings 2>/dev/null)" ] && [ ! -e /swapfile ]; then
202
+ step 'Aggiungo 2 GB di memoria di scambio (il server ha poca memoria)'
203
+ if quiet fallocate -l 2G /swapfile && chmod 600 /swapfile && quiet mkswap /swapfile && quiet swapon /swapfile; then
204
+ echo '/swapfile none swap sw 0 0' >>/etc/fstab
205
+ else
206
+ rm -f /swapfile
207
+ warn 'Non sono riuscito ad aggiungerla: la prima generazione del sito potrebbe essere lenta.'
208
+ fi
209
+ fi
210
+ }
211
+
212
+ install_node() {
213
+ local current
214
+ current=$(node -v 2>/dev/null | sed 's/^v\([0-9]*\).*/\1/' || true)
215
+ if [ "${current:-0}" -ge "$NODE_MAJOR" ] 2>/dev/null; then
216
+ ok "Node.js $(node -v) già installato"
217
+ return
218
+ fi
219
+ step "Installo Node.js $NODE_MAJOR"
220
+ install -d -m 0755 /etc/apt/keyrings
221
+ curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor --yes -o /etc/apt/keyrings/nodesource.gpg
222
+ echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" >/etc/apt/sources.list.d/nodesource.list
223
+ # The distribution's own nodejs must not win over this one.
224
+ printf 'Package: nodejs\nPin: origin deb.nodesource.com\nPin-Priority: 600\n' >/etc/apt/preferences.d/nodejs
225
+ apt_get update
226
+ apt_get install nodejs
227
+ ok "Node.js $(node -v)"
228
+ }
229
+
230
+ install_caddy() {
231
+ if command -v caddy >/dev/null; then
232
+ ok "Caddy $(caddy version | awk '{ print $1 }') già installato"
233
+ return
234
+ fi
235
+ step 'Installo Caddy, che serve il sito in HTTPS'
236
+ curl -fsSL https://dl.cloudsmith.io/public/caddy/stable/gpg.key | gpg --dearmor --yes -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
237
+ curl -fsSL https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt >/etc/apt/sources.list.d/caddy-stable.list
238
+ apt_get update
239
+ apt_get install caddy
240
+ ok "Caddy $(caddy version | awk '{ print $1 }')"
241
+ }
242
+
243
+ # The system's security fixes install by themselves; Materialize updates itself from the panel.
244
+ install_updates() {
245
+ if ! dpkg -s unattended-upgrades >/dev/null 2>&1; then
246
+ step 'Attivo gli aggiornamenti di sicurezza automatici del sistema'
247
+ apt_get install unattended-upgrades
248
+ fi
249
+ [ -f /etc/apt/apt.conf.d/20auto-upgrades ] ||
250
+ printf 'APT::Periodic::Update-Package-Lists "1";\nAPT::Periodic::Unattended-Upgrade "1";\n' >/etc/apt/apt.conf.d/20auto-upgrades
251
+ }
252
+
253
+ create_user() {
254
+ id "$USER_NAME" >/dev/null 2>&1 ||
255
+ useradd --system --home-dir "$HOME_DIR" --create-home --shell /usr/sbin/nologin "$USER_NAME"
256
+ install -d -o "$USER_NAME" -g "$USER_NAME" -m 0750 "$HOME_DIR"
257
+ }
258
+
259
+ create_site() {
260
+ if [ -f "$DIR/materialize.config.ts" ] || [ -f "$DIR/materialize.config.js" ]; then
261
+ ok "Il sito c’è già in $DIR: lo lascio com’è"
262
+ else
263
+ step "Creo il sito in $DIR ($([ "$VERSION" = latest ] && echo 'l’ultima versione di Materialize' || echo "Materialize $VERSION"))"
264
+ install -d -o "$USER_NAME" -g "$USER_NAME" -m 0750 "$DIR"
265
+ (cd "$DIR" && as_site npx --yes "create-materialize@$VERSION" . --no-install >>"$LOG" 2>&1)
266
+ # The updates from the panel use the same registry.
267
+ if [ -n "$REGISTRY" ]; then
268
+ echo "registry=$REGISTRY" >"$DIR/.npmrc"
269
+ chown "$USER_NAME:" "$DIR/.npmrc"
270
+ fi
271
+ fi
272
+ if [ ! -x "$DIR/node_modules/.bin/materialize" ]; then
273
+ step 'Scarico i pacchetti del sito (qualche minuto)'
274
+ (cd "$DIR" && as_site npm install --no-audit --no-fund >>"$LOG" 2>&1)
275
+ fi
276
+ ok "Materialize $(node -p "require('$DIR/node_modules/@materializecms/cli/package.json').version")"
277
+ }
278
+
279
+ write_service() {
280
+ install -d -m 0750 /etc/materialize
281
+ # The site's own secrets (ANTHROPIC_API_KEY…), kept when this script runs again.
282
+ [ -f /etc/materialize/env ] ||
283
+ printf '# Variabili per il sito, una per riga (NOME=valore). Poi: systemctl restart materialize\n' >/etc/materialize/env
284
+ chmod 0640 /etc/materialize/env
285
+ chgrp "$USER_NAME" /etc/materialize/env
286
+ cat >/etc/systemd/system/materialize.service <<EOF
287
+ # Scritto da install.sh di Materialize: rilanciandolo viene riscritto.
288
+ [Unit]
289
+ Description=Materialize ($DOMAIN)
290
+ After=network-online.target
291
+ Wants=network-online.target
292
+
293
+ [Service]
294
+ User=$USER_NAME
295
+ Group=$USER_NAME
296
+ WorkingDirectory=$DIR
297
+ ExecStart=$DIR/node_modules/.bin/materialize start
298
+ Environment=NODE_ENV=production
299
+ Environment=HOME=$HOME_DIR
300
+ Environment=MATERIALIZE_HOST=127.0.0.1
301
+ Environment=MATERIALIZE_PORT=$PORT
302
+ Environment=MATERIALIZE_TRUST_PROXY=1
303
+ Environment=MATERIALIZE_SITE_URL=https://$DOMAIN
304
+ EnvironmentFile=-/etc/materialize/env
305
+ Restart=always
306
+ RestartSec=5
307
+ TimeoutStopSec=30
308
+ # The site writes only in its own folder and in its home (npm's cache).
309
+ NoNewPrivileges=true
310
+ PrivateTmp=true
311
+ ProtectSystem=strict
312
+ ProtectHome=true
313
+ ReadWritePaths=$DIR $HOME_DIR
314
+ ProtectKernelTunables=true
315
+ ProtectControlGroups=true
316
+
317
+ [Install]
318
+ WantedBy=multi-user.target
319
+ EOF
320
+ systemctl daemon-reload
321
+ }
322
+
323
+ write_caddy() {
324
+ local site=/etc/caddy/materialize.caddy main=/etc/caddy/Caddyfile
325
+ {
326
+ echo '# Scritto da install.sh di Materialize: rilanciandolo viene riscritto.'
327
+ echo "# dominio: $DOMAIN"
328
+ echo "$DOMAIN {"
329
+ if [ "$TLS" = internal ]; then echo ' tls internal'; elif [ -n "$EMAIL" ]; then echo " tls $EMAIL"; fi
330
+ echo ' encode zstd gzip'
331
+ echo " reverse_proxy 127.0.0.1:$PORT"
332
+ echo '}'
333
+ if [ "$WWW" = 1 ]; then
334
+ echo
335
+ echo "www.$DOMAIN {"
336
+ echo " redir https://$DOMAIN{uri} permanent"
337
+ echo '}'
338
+ fi
339
+ } >"$site"
340
+ # The stock Caddyfile (Caddy's welcome page) is replaced; one written by someone is kept, with ours imported.
341
+ if [ ! -f "$main" ] || grep -q '/usr/share/caddy' "$main"; then
342
+ printf '# I siti serviti da Caddy. Materialize è in materialize.caddy.\nimport materialize.caddy\n' >"$main"
343
+ elif ! grep -q 'import materialize.caddy' "$main"; then
344
+ printf '\nimport materialize.caddy\n' >>"$main"
345
+ fi
346
+ quiet caddy validate --config "$main" --adapter caddyfile
347
+ quiet systemctl enable caddy
348
+ quiet systemctl reload-or-restart caddy
349
+ ok "Caddy serve $DOMAIN in HTTPS"
350
+ }
351
+
352
+ open_firewall() {
353
+ [ "$FIREWALL" = 1 ] || return 0
354
+ if ! command -v ufw >/dev/null; then apt_get install ufw; fi
355
+ # Every port sshd listens on stays open, so this session is never cut off.
356
+ local ports
357
+ # Either tool may be missing on a minimal image: what it does not say, the other does.
358
+ ports=$({
359
+ sshd -T 2>/dev/null | awk '$1 == "port" { print $2 }' || true
360
+ ss -Htlnp 2>/dev/null | awk '/"sshd"/ { n = split($4, a, ":"); print a[n] }' || true
361
+ } | sort -un)
362
+ [ -n "$ports" ] || ports=22
363
+ {
364
+ local p
365
+ for p in $ports; do ufw allow "$p/tcp" comment 'SSH'; done
366
+ ufw allow 80/tcp comment 'HTTP'
367
+ ufw allow 443/tcp comment 'HTTPS'
368
+ ufw allow 443/udp comment 'HTTP/3'
369
+ ufw --force enable
370
+ } >>"$LOG" 2>&1 && ok "Firewall attivo: aperti solo SSH ($(echo $ports | tr ' ' ',')), HTTP e HTTPS" ||
371
+ warn 'Non sono riuscito ad attivare il firewall (ufw): controlla quello del fornitore del VPS.'
372
+ }
373
+
374
+ start_site() {
375
+ if systemctl is-active --quiet materialize; then
376
+ step 'Riavvio Materialize'
377
+ systemctl restart materialize
378
+ else
379
+ step 'Avvio Materialize: la prima volta genera il sito (un paio di minuti)'
380
+ quiet systemctl enable --now materialize
381
+ fi
382
+ local i
383
+ for i in $(seq 1 300); do
384
+ # The panel answers at once, the site once it is built (until then: 503).
385
+ if curl -fsS --max-time 2 "http://127.0.0.1:$PORT/api/health" >/dev/null 2>&1 &&
386
+ [ "$(curl -s --max-time 5 -o /dev/null -w '%{http_code}' "http://127.0.0.1:$PORT/")" != 503 ]; then
387
+ ok 'Materialize è acceso e il sito è pronto'
388
+ return
389
+ fi
390
+ if [ "$i" -gt 20 ] && ! systemctl is-active --quiet materialize; then break; fi
391
+ sleep 1
392
+ done
393
+ printf '\n %s✗ Materialize non parte.%s Le ultime righe del suo registro:\n\n' "$R" "$N" >&2
394
+ journalctl -u materialize -n 20 --no-pager 2>/dev/null | sed 's/^/ /' >&2
395
+ printf '\n Il registro completo: journalctl -u materialize\n\n' >&2
396
+ exit 1
397
+ }
398
+
399
+ finish() {
400
+ local secure=0 i insecure=''
401
+ [ "$TLS" = internal ] && insecure=-k
402
+ # Caddy asks Let's Encrypt for the certificate on its first start: it takes a few seconds.
403
+ for i in $(seq 1 30); do
404
+ if curl -fsS $insecure --max-time 5 --resolve "$DOMAIN:443:127.0.0.1" "https://$DOMAIN/api/health" >/dev/null 2>&1; then
405
+ secure=1
406
+ break
407
+ fi
408
+ sleep 2
409
+ done
410
+ echo
411
+ if [ "$secure" = 1 ]; then
412
+ ok "https://$DOMAIN è online"
413
+ else
414
+ warn "Il certificato HTTPS per $DOMAIN non è ancora pronto."
415
+ note 'Arriva da solo quando il dominio punta a questo server e le porte 80 e 443 sono aperte'
416
+ note "(anche nel firewall del fornitore). Per vedere cosa succede: journalctl -u caddy -f"
417
+ fi
418
+ local code
419
+ code=$(cat "$DIR/.materialize/install-code" 2>/dev/null || true)
420
+ echo
421
+ if [ -n "$code" ]; then
422
+ printf ' %sManca un passo:%s apri questo link e crea il tuo account.\n\n' "$B" "$N"
423
+ printf ' %shttps://%s/admin/?installa=%s%s\n\n' "$B$C" "$DOMAIN" "$code" "$N"
424
+ note 'Il link vale finché non hai finito: se lo perdi, rilancia questo comando.'
425
+ else
426
+ printf ' %sMaterialize è installato:%s https://%s/admin\n' "$B" "$N" "$DOMAIN"
427
+ fi
428
+ echo
429
+ printf ' %sDa sapere%s\n' "$B" "$N"
430
+ note "Il sito è in $DIR, i contenuti in $DIR/.materialize"
431
+ note 'Gli aggiornamenti si installano dal pannello: Impostazioni → Aggiornamenti'
432
+ note 'Stato: systemctl status materialize Registro: journalctl -u materialize -f'
433
+ note 'Variabili per il sito (chiavi AI…): /etc/materialize/env'
434
+ echo
435
+ }
436
+
437
+ main "$@"
package/package.json CHANGED
@@ -1,6 +1,56 @@
1
1
  {
2
2
  "name": "@materializecms/cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Il comando materialize: sviluppo, produzione con aggiornamenti dal pannello, generazione del sito, backup, MCP.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/materializecms/materialize.git",
10
+ "directory": "packages/cli"
11
+ },
12
+ "homepage": "https://github.com/materializecms/materialize#readme",
13
+ "bugs": "https://github.com/materializecms/materialize/issues",
14
+ "bin": {
15
+ "materialize": "src/cli.js"
16
+ },
17
+ "exports": {
18
+ ".": {
19
+ "types": "./src/load-config.d.ts",
20
+ "default": "./src/load-config.js"
21
+ }
22
+ },
23
+ "dependencies": {
24
+ "@materializecms/astro": "0.2.0",
25
+ "@materializecms/core": "0.2.0",
26
+ "@materializecms/db": "0.2.0",
27
+ "@materializecms/server": "0.2.0",
28
+ "@modelcontextprotocol/sdk": "1.32.2"
29
+ },
30
+ "peerDependencies": {
31
+ "astro": "^7.0.0"
32
+ },
33
+ "peerDependenciesMeta": {
34
+ "astro": {
35
+ "optional": true
36
+ }
37
+ },
38
+ "engines": {
39
+ "node": ">=24"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "changelog": [
45
+ {
46
+ "version": "0.2.0",
47
+ "date": "2026-11-01",
48
+ "notes": "- **Password dimenticata?** nella pagina di accesso: un link per email, valido un'ora. Senza email configurata, il comando `materialize reset-password` stampa il link sul server, anche per l'unico amministratore.\n- **Cronologia per le pagine sulla tela**: ogni versione si guarda su desktop, tablet e mobile prima di ripristinarla.\n- **Esporta la pagina in PDF o PNG** dall'editor, per mandarla al cliente: desktop, tablet e mobile, ognuno come un'unica immagine lunga.\n- **Guida per chi usa Materialize**, in `docs/guida`: installazione, editor, contenuti, lingue, SEO, persone, aggiornamenti, backup e problemi comuni.\n- **Sicurezza**: i link degli inviti e delle nuove password puntano sempre all'indirizzo pubblico del sito, mai a quello dichiarato da chi fa la richiesta. Consigliato aggiornare."
49
+ },
50
+ {
51
+ "version": "0.1.0",
52
+ "date": "2026-10-31",
53
+ "notes": "La prima versione pubblica.\n\n- **Editor visuale**: pagine disegnate su una tela, con sezioni, colonne, testi, immagini, video, icone e forme; componenti riutilizzabili con varianti; righelli, guide, allineamenti e misure; modalità Contenuti per chi modifica solo i testi.\n- **Animazioni ed effetti**: animazioni allo scorrimento e con linee temporali, stati al passaggio del mouse, sfondi WebGL e oggetti 3D.\n- **Contenuti**: raccolte come Progetti, Diario e Team, con pagine modello; azioni rapide e selezione multipla negli elenchi e nei Media.\n- **Siti in più lingue**: traduzioni collegate tra loro, anche con l’AI, e un selettore di lingua per il sito.\n- **SEO**: titoli e descrizioni, sitemap, controlli sulla pagina e reindirizzamenti automatici quando cambia un indirizzo.\n- **Moduli di contatto** con invio delle email dal tuo server di posta.\n- **Assistente AI e server MCP**: testi, traduzioni e modifiche alle pagine chiedendole a parole.\n- **Importazione da WordPress**: pagine, articoli e immagini.\n- **Statistiche e cookie**: Google Analytics, Tag Manager, Meta Pixel, Plausible, Matomo e codice personalizzato, con il banner per il consenso.\n- **Installazione su VPS con un comando**: Node.js, HTTPS con Caddy, servizio che riparte da solo, firewall e link per finire nel browser.\n- **Immagine Docker** con `compose.yaml` pronto per l'HTTPS: il sito sta in un volume, quindi gli aggiornamenti dal pannello restano anche ricreando il container.\n- **Installazione nel browser**: controllo del server, nome e lingua del sito, account amministratore, passo per passo.\n- **Aggiornamenti con un clic** da Impostazioni → Aggiornamenti, con backup prima e ritorno automatico alla versione precedente se la nuova non parte; le correzioni possono installarsi da sole.\n- **Sicurezza**: verifica in due passaggi, pausa dopo i tentativi di accesso falliti, registro delle attività, backup giornalieri del database e dei media."
54
+ }
55
+ ]
56
+ }
package/src/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/src/cli.js ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * The materialize command. `start` runs under a small supervisor that keeps the server alive and
4
+ * applies updates from the panel; it is loaded on its own, before any Materialize package, so an
5
+ * update can replace those packages under it. Everything else lives in commands.ts.
6
+ */
7
+ import { fileURLToPath } from 'node:url';
8
+ const [command, ...args] = process.argv.slice(2);
9
+ if (command === 'start' && !process.env.MATERIALIZE_SUPERVISED && !args.includes('--no-supervisor')) {
10
+ const { supervise } = await import('./supervisor.js');
11
+ await supervise(fileURLToPath(import.meta.url), args);
12
+ }
13
+ else {
14
+ await import('./commands.js');
15
+ }
@@ -0,0 +1 @@
1
+ export {};