Documentazione / Installazione

Installazione

BlueGem CRM è pensato per girare su un singolo server Linux con PostgreSQL locale: un'API in Go più un worker in background per workflow, email e manutenzione. Sul server arriva un artefatto binario, non il codice sorgente: non serve nessun runtime. Questa pagina copre tutto il ciclo di vita: prima installazione, aggiornamenti, backup e ripristino.

01Requisiti

02Il database PostgreSQL

Con il database sulla stessa macchina non c'è niente da preparare a mano: setup.sh installa PostgreSQL, crea il ruolo (crm_user) e il database di sua proprietà (crm_db) con la password che gli passi in --db-pass, e allinea il ruolo a quella password a ogni rilancio.

Con il database su una macchina separata, due comandi, uno per macchina:

bash · database su un'altra macchina
# sulla macchina del database: abilita l'accesso dal SOLO indirizzo dell'app
sudo bash scripts/ops/setup-db.sh --db-name crm_db --db-user crm_user \
     --db-pass '<password>' --allow-host <ip-app>

# sulla macchina dell'applicazione
sudo /opt/crm/scripts/ops/setup.sh --skip-db --db-host <ip-db> --db-pass '<password>'

03Prima installazione

L'artefatto di rilascio si produce sulla macchina di build (dove servono Go e, solo per il CSS, Node):

bash · sulla macchina di build
scripts/ops/build-release.sh   # -> dist/crm-<versione>.tar.gz + .sha256

Contiene i binari (crmgo-server, crmgo-worker, crmgo-migrate, crmgo-bootstrap), il frontend già compilato, le unit systemd, gli script di esercizio e un file VERSION con il commit da cui è stato prodotto. Le migrazioni non sono file da copiare: viaggiano dentro il binario. Accanto all'archivio la build scrive l'impronta .sha256: copiala insieme all'archivio.

Sul server, l'app gira come utente di sistema dedicato (crm), senza shell, con l'installazione in /opt/crm:

bash · messa in servizio
sudo mkdir -p /opt/crm
sha256sum -c crm-<versione>.tar.gz.sha256   # deve dire OK
sudo tar xzf crm-<versione>.tar.gz -C /opt/crm
sudo /opt/crm/scripts/ops/setup.sh --db-pass '<password-db>' --admin-pass '<password-admin>'

Un comando solo. setup.sh fa, in ordine: PostgreSQL con ruolo e database, utente di sistema e permessi, .env con i segreti mancanti generati, migrazioni (le applica crmgo-migrate), primo amministratore (crmgo-bootstrap), backup pianificati con il primo giro eseguito subito, sentinella locale, irrobustimento SSH, unit systemd crm-go-app e crm-go-worker, avvio e health check su /ready. Se l'archivio non corrisponde alla sua impronta, si ferma prima di installare qualsiasi cosa.

OpzioneDefaultA cosa serve
--db-passobbligatoriaPassword del ruolo applicativo, alla prima installazione.
--admin-passgenerataPassword del primo amministratore. Se non la passi, viene generata e stampata una volta sola: annotala.
--admin-user / --admin-emailadminIdentità del primo amministratore.
--db-host / --db-portlocalhost / 5432Database su un'altra macchina (con --skip-db).
--addr:5000Indirizzo di ascolto dell'app.
--backup-tierstandardbasic (giornaliero), standard (+ settimanale), premium (+ orario e mensile).
--no-restartriavvio attivoNon riavviare l'app a fine messa in servizio: serve a scegliere tu il momento del fermo.
Nota

Non esiste un utente admin predefinito: il primo amministratore lo crea crmgo-bootstrap, chiamato da setup.sh, e deve cambiare la password al primo accesso. Nessuna credenziale di default da ricordarsi di cambiare.

Lo script è idempotente ed è pensato per essere rilanciato: non rigenera i segreti già presenti, non ricrea l'amministratore se esiste, non sovrascrive una configurazione dei backup scritta a mano.

Attenzione

Finita la messa in servizio, il servizio ascolta solo su 127.0.0.1, in chiaro. Non è ancora consegnabile: servono nome a dominio, reverse proxy con certificato e HSTS (sezione 05).

04Configurazione (.env)

Tutta la configurazione vive in un unico file .env, che lo scrive l'installer: genera i valori mancanti e conserva quelli già presenti. Le chiavi che contano:

VariabileValoreNote
APP_ENVproductionAttiva i controlli di sicurezza di produzione: senza segreti validi l'app rifiuta di partire.
GO_HTTP_ADDR:5000Indirizzo e porta su cui ascolta l'app (dietro il proxy).
DB_HOST / DB_PORT / DB_USER / DB_PASSWORD / DB_NAMECredenziali del ruolo e del database (sezione 02). Il ruolo è a privilegio minimo: non serve CREATE DATABASE.
DB_SCHEMApublicLo schema in cui vivono le tabelle; viene creato alla prima migrazione.
AUTH_JWT_SECRET≥ 32 byteL'unico segreto di firma dei token. Lo genera l'installer; in produzione l'app non parte se manca, è troppo corto o è il default di sviluppo.
CONNECTIONS_ENC_KEY≥ 32 caratteriChiave di cifratura dei segreti a riposo (credenziali SMTP, S3, provider di fatturazione, secret TOTP). Obbligatoria in produzione.
CLIENT_DIR / ATTACHMENTS_DIRFrontend da servire e cartella degli allegati su disco locale.
TRUST_PROXYtrueDa impostare solo dopo aver messo il proxy davanti: vedi sotto.
Sicurezza · TRUST_PROXY

TRUST_PROXY si scrive true o false, mai un numero: non è il numero di hop. Un valore numerico è un errore di configurazione e il server non parte, con un messaggio che dice cosa scrivere.

true è corretto soltanto con un proxy che riscrive X-Forwarded-For come negli esempi della sezione 05. Con un proxy che lo appende, un client può dichiarare l'indirizzo che vuole: il limite per IP sul login smette di funzionare e l'indirizzo registrato nell'audit è quello scelto da lui.

Non rigenerare i segreti

AUTH_JWT_SECRET e CONNECTIONS_ENC_KEY si generano una volta sola. Rigenerarli invalida tutte le sessioni attive e rende illeggibili i segreti già cifrati a riposo.

05Reverse proxy e TLS

Davanti all'app va un proxy che termina il TLS. Esempio minimo con nginx:

nginx · esempio
server {
  listen 443 ssl;
  server_name crm.tuodominio.it;
  # ssl_certificate / ssl_certificate_key gestiti da certbot o simili
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
  client_max_body_size 30m;   # deve superare il limite sugli allegati

  location / {
    proxy_pass http://127.0.0.1:5000;
    # RISCRIVE, non appende: è il presupposto di TRUST_PROXY=true
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $host;
  }
}
HSTS lo mette il proxy

L'header Strict-Transport-Security non lo imposta l'applicazione: parla in chiaro con il proxy e non può sapere se davanti c'è TLS. Va messo nella configurazione del proxy, come nell'esempio, oppure non lo mette nessuno. Con Caddy il certificato e il rinnovo sono automatici, l'header va comunque dichiarato.

Solo dopo aver messo il proxy davanti, imposta TRUST_PROXY=true nel .env e riavvia. La CSP dell'app è strict e non ammette host esterni; il CSS arriva già compilato nell'artefatto, quindi sul server non c'è niente da costruire.

06Aggiornare l'istanza

Stesso comando dell'installazione, sull'artefatto nuovo. Il backup non è automatico: è un passo esplicito, ed è il primo.

bash · aggiornamento
cd /opt/crm
sudo -u crm scripts/ops/backup-db.sh   # PRIMA di toccare lo schema
sudo tar xzf crm-<versione-nuova>.tar.gz -C /opt/crm
sudo /opt/crm/scripts/ops/setup.sh --db-pass '<password-db>'

setup.sh riapplica le migrazioni, riavvia i servizi (altrimenti resterebbe in esecuzione il binario precedente, e la macchina risulterebbe aggiornata senza esserlo) e si ferma con errore se /ready non risponde: un aggiornamento rotto si vede subito. Con --no-restart scegli tu il momento del fermo.

Migrazioni: solo in avanti

Le migrazioni vanno solo in avanti, per scelta: non esiste un down. Ognuna gira dentro una transazione, e la rete di sicurezza è il backup del primo passo, non un percorso a ritroso: un down ricostruirebbe la forma delle tabelle, non i dati.

Ne segue che rimettere il binario precedente non basta se lo schema è già cambiato. Se l'installer è fallito prima del passo delle migrazioni (il log in /opt/crm/logs/setup/ stampa i passi come ==> [4/9] Migrazioni) basta riscompattare l'artefatto precedente; se le migrazioni sono già passate, il rollback è ripristino del dump + artefatto precedente, e costa i dati scritti dopo il dump.

07Backup e ripristino

I backup pianificati li installa l'installer: un timer systemd (crm-backup.timer) che chiama scripts/ops/backup-run.sh con il livello scelto, più un primo giro eseguito subito, così la macchina nasce con almeno un punto di ripristino. Non serve scrivere righe di cron a mano, e non conviene farlo.

Attenzione

Porta i backup fuori dal server (object storage o altro host): un backup sullo stesso disco non sopravvive alla perdita del disco. La modalità locale è un punto di partenza, non un arrivo.

Per il ripristino, la regola è: prima in un database di prova, verifichi, poi tagli sul database vivo:

bash · restore
# con un ruolo che può creare database (es. postgres)
scripts/ops/restore-db.sh backups/crm-<db>-<data>.dump crm_restore_check
# ispezioni crm_restore_check; se ok, ripristini sul DB vivo (serve --force):
scripts/ops/restore-db.sh backups/crm-<db>-<data>.dump <db-vivo> --force
# in emergenza, per riaprire prima il servizio (indici di ricerca ricostruiti dopo):
scripts/ops/restore-db.sh backups/crm-<db>-<data>.dump <db-vivo> --force --due-fasi

restore-db.sh rifiuta di sovrascrivere il database vivo senza --force, carica in un database di appoggio e scambia i nomi solo a caricamento riuscito: se il dump è illeggibile, quello vivo non viene toccato. Il database precedente resta col suffisso _precedente_<data> finché non lo cancelli a mano. Il ciclo dump e restore è scriptato e viene rieseguito dalla verifica automatica a ogni giro.

08Worker e salute operativa

Il worker esegue la coda dei workflow, lo scheduler, il recupero dei job bloccati e il poller delle email in ingresso. Ogni ciclo è protetto da un advisory lock PostgreSQL: un solo worker è sicuro anche attraverso i riavvii.

09Checklist go-live

Prima di esporre l'istanza a utenti reali:

#Controllo
1APP_ENV=production nel .env.
2Impronta .sha256 dell'archivio verificata prima di scompattarlo.
3AUTH_JWT_SECRET e CONNECTIONS_ENC_KEY presenti e generati dall'installer (non i valori di esempio).
4TRUST_PROXY=true solo dietro un proxy che riscrive X-Forwarded-For; mai un numero.
5Credenziali DB reali; ruolo a privilegio minimo (niente CREATE DATABASE).
6TLS attivo davanti (nginx/Caddy) e header HSTS dichiarato nel proxy: l'app non lo imposta.
7crm-backup.timer attivo e copie fuori dal server (BACKUP_MODE=s3).
8Prova di ripristino fatta almeno una volta su un database di scorta.
9crm-go-app e crm-go-worker entrambi enabled e running.
10Primo admin creato dall'installer, password cambiata al primo accesso; nessun utente di seed presente.
11Smoke test: login, una lista, un dettaglio, GET /api/ops/healthstatus: ok.