@materializecms/cli 0.0.0-stage → 0.1.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 +24 -0
- package/LICENSE +21 -0
- package/README.md +20 -2
- package/compose.yaml +35 -0
- package/install.sh +437 -0
- package/package.json +49 -4
- package/src/cli.d.ts +2 -0
- package/src/cli.js +15 -0
- package/src/commands.d.ts +1 -0
- package/src/commands.js +365 -0
- package/src/load-config.d.ts +13 -0
- package/src/load-config.js +53 -0
- package/src/site.d.ts +38 -0
- package/src/site.js +193 -0
- package/src/supervisor.d.ts +29 -0
- package/src/supervisor.js +169 -0
- package/src/updates.d.ts +14 -0
- package/src/updates.js +169 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
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.1.0 — 2026-10-31
|
|
8
|
+
|
|
9
|
+
La prima versione pubblica.
|
|
10
|
+
|
|
11
|
+
- **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.
|
|
12
|
+
- **Animazioni ed effetti**: animazioni allo scorrimento e con linee temporali, stati al passaggio del mouse, sfondi WebGL e oggetti 3D.
|
|
13
|
+
- **Contenuti**: raccolte come Progetti, Diario e Team, con pagine modello; azioni rapide e selezione multipla negli elenchi e nei Media.
|
|
14
|
+
- **Siti in più lingue**: traduzioni collegate tra loro, anche con l’AI, e un selettore di lingua per il sito.
|
|
15
|
+
- **SEO**: titoli e descrizioni, sitemap, controlli sulla pagina e reindirizzamenti automatici quando cambia un indirizzo.
|
|
16
|
+
- **Moduli di contatto** con invio delle email dal tuo server di posta.
|
|
17
|
+
- **Assistente AI e server MCP**: testi, traduzioni e modifiche alle pagine chiedendole a parole.
|
|
18
|
+
- **Importazione da WordPress**: pagine, articoli e immagini.
|
|
19
|
+
- **Statistiche e cookie**: Google Analytics, Tag Manager, Meta Pixel, Plausible, Matomo e codice personalizzato, con il banner per il consenso.
|
|
20
|
+
- **Installazione su VPS con un comando**: Node.js, HTTPS con Caddy, servizio che riparte da solo, firewall e link per finire nel browser.
|
|
21
|
+
- **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.
|
|
22
|
+
- **Installazione nel browser**: controllo del server, nome e lingua del sito, account amministratore, passo per passo.
|
|
23
|
+
- **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.
|
|
24
|
+
- **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
|
-
#
|
|
1
|
+
# @materializecms/cli
|
|
2
2
|
|
|
3
|
-
|
|
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,35 @@
|
|
|
1
|
+
# Materialize with HTTPS, on a server where Docker is already there:
|
|
2
|
+
#
|
|
3
|
+
# DOMAIN=example.com docker compose up -d
|
|
4
|
+
# docker compose logs materialize (the link to finish the installation in the browser)
|
|
5
|
+
#
|
|
6
|
+
# Caddy gets the Let's Encrypt certificate for the domain, which must already point to the server.
|
|
7
|
+
# The site and its content stay in the "site" volume; updates are installed from the panel.
|
|
8
|
+
|
|
9
|
+
services:
|
|
10
|
+
materialize:
|
|
11
|
+
image: ${MATERIALIZE_IMAGE:-ghcr.io/materializecms/materialize:latest}
|
|
12
|
+
restart: unless-stopped
|
|
13
|
+
volumes:
|
|
14
|
+
- site:/site
|
|
15
|
+
environment:
|
|
16
|
+
MATERIALIZE_SITE_URL: https://${DOMAIN:?Indica il dominio, per esempio DOMAIN=example.com docker compose up -d}
|
|
17
|
+
MATERIALIZE_TRUST_PROXY: "1"
|
|
18
|
+
# Other variables for the site go here, for example ANTHROPIC_API_KEY.
|
|
19
|
+
|
|
20
|
+
caddy:
|
|
21
|
+
image: caddy:2
|
|
22
|
+
restart: unless-stopped
|
|
23
|
+
command: caddy reverse-proxy --from ${DOMAIN} --to materialize:4321
|
|
24
|
+
ports:
|
|
25
|
+
- "80:80"
|
|
26
|
+
- "443:443"
|
|
27
|
+
- "443:443/udp"
|
|
28
|
+
volumes:
|
|
29
|
+
- caddy:/data
|
|
30
|
+
depends_on:
|
|
31
|
+
- materialize
|
|
32
|
+
|
|
33
|
+
volumes:
|
|
34
|
+
site:
|
|
35
|
+
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,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@materializecms/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.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.1.0",
|
|
25
|
+
"@materializecms/core": "0.1.0",
|
|
26
|
+
"@materializecms/db": "0.1.0",
|
|
27
|
+
"@materializecms/server": "0.1.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.1.0",
|
|
47
|
+
"date": "2026-10-31",
|
|
48
|
+
"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."
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
package/src/cli.d.ts
ADDED
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 {};
|