iride-setup 0.1.0 - bootstrap, install, first_setup, health check
This commit is contained in:
commit
5dd160472d
|
|
@ -0,0 +1,3 @@
|
||||||
|
*.tar.gz
|
||||||
|
.DS_Store
|
||||||
|
__pycache__/
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
# iride-setup
|
||||||
|
|
||||||
|
Bootstrap e primo install di un'istanza **IRIDE** (Tecnotel Servizi SRL), sullo
|
||||||
|
schema di `argos-setup`. Il repository applicativo è `iride`; gli aggiornamenti
|
||||||
|
successivi li fa `iride/scripts/update.sh`.
|
||||||
|
|
||||||
|
## Install su un server Ubuntu LTS pulito
|
||||||
|
|
||||||
|
```text
|
||||||
|
curl -fsSL -H "Authorization: token <TOKEN>" \
|
||||||
|
https://repo.argosdefense.io/tecnotel/iride-setup/raw/branch/main/bootstrap.sh \
|
||||||
|
| sudo bash -s -- --token <TOKEN>
|
||||||
|
```
|
||||||
|
|
||||||
|
`<TOKEN>` è un token Gitea di **sola lettura dedicato all'istanza** (uno per
|
||||||
|
cliente, revocabile), mai un token personale. Resta in
|
||||||
|
`/opt/iride/config/git-credentials` (0600, utente `iride`) e serve a `update.sh`.
|
||||||
|
|
||||||
|
Non interattivo (VM di test, CI):
|
||||||
|
|
||||||
|
```text
|
||||||
|
... | sudo bash -s -- --token <TOKEN> --client "Nome Cliente" --domain iride.cliente.it --admin-password '<min 12 caratteri>'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cosa fa
|
||||||
|
|
||||||
|
| Passo | Script | Esito |
|
||||||
|
|---|---|---|
|
||||||
|
| pacchetti, utente `iride`, `/opt/iride/*`, credenziali git | `bootstrap.sh` | clona questo repo in `/opt/iride/setup` |
|
||||||
|
| clone di `iride` in `/opt/iride/app`, venv, dipendenze | `install.sh` | |
|
||||||
|
| configurazione da `config/*.example` + wizard | `first_setup.py` | `iride.json`, `users.json` (0600) |
|
||||||
|
| migrazioni, build frontend (se presente), unit systemd, nginx + certificato | `install.sh` | servizi abilitati e avviati |
|
||||||
|
| verifica | `checks/health.sh` | exit 1 se qualcosa non va |
|
||||||
|
|
||||||
|
Layout risultante: `/opt/iride/{app,config,data,logs,backups,certs,setup}`.
|
||||||
|
|
||||||
|
## Note
|
||||||
|
|
||||||
|
- Il certificato generato è self-signed: **WhatsApp richiede un certificato
|
||||||
|
valido** (Let's Encrypt o del cliente) in `/opt/iride/certs/{fullchain,privkey}.pem`.
|
||||||
|
- Il wizard web (porta 8888, stile ARGOS) e la validazione della licenza
|
||||||
|
all'install arrivano dopo il pilota (B-055 li collauda su VM di test).
|
||||||
|
- Riesecuzione sicura: `bootstrap.sh` aggiorna il setup e rilancia `install.sh`,
|
||||||
|
che non tocca configurazione, utenti e certificati già presenti.
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# IRIDE — Bootstrap installer (Tecnotel Servizi SRL)
|
||||||
|
#
|
||||||
|
# Uso su un server Ubuntu LTS pulito:
|
||||||
|
#
|
||||||
|
# curl -fsSL -H "Authorization: token <TOKEN>" \
|
||||||
|
# https://repo.argosdefense.io/tecnotel/iride-setup/raw/branch/main/bootstrap.sh \
|
||||||
|
# | sudo bash -s -- --token <TOKEN>
|
||||||
|
#
|
||||||
|
# <TOKEN> = token Gitea di SOLA LETTURA dedicato all'istanza (mai personale).
|
||||||
|
# Opzioni: --gitea-url URL (default https://repo.argosdefense.io)
|
||||||
|
# --org ORG (default tecnotel) --branch BRANCH (default main)
|
||||||
|
# --setup-branch BRANCH (default main)
|
||||||
|
# --client NOME --domain FQDN --admin-password PWD (non interattivo)
|
||||||
|
# Riesecuzione sicura: aggiorna iride-setup e rilancia install.sh.
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
GITEA_URL="https://repo.argosdefense.io"
|
||||||
|
ORG="tecnotel"
|
||||||
|
BRANCH="main"
|
||||||
|
SETUP_BRANCH="main"
|
||||||
|
TOKEN=""
|
||||||
|
PASSTHROUGH=()
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--token) TOKEN="$2"; shift 2 ;;
|
||||||
|
--gitea-url) GITEA_URL="$2"; shift 2 ;;
|
||||||
|
--org) ORG="$2"; shift 2 ;;
|
||||||
|
--branch) BRANCH="$2"; shift 2 ;;
|
||||||
|
--setup-branch) SETUP_BRANCH="$2"; shift 2 ;;
|
||||||
|
--client|--domain|--admin-password|--admin-user) PASSTHROUGH+=("$1" "$2"); shift 2 ;;
|
||||||
|
*) echo "Opzione sconosciuta: $1"; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ $EUID -eq 0 ]] || { echo "Eseguire con sudo"; exit 1; }
|
||||||
|
[[ -n "$TOKEN" ]] || { echo "Serve --token <token Gitea di sola lettura>"; exit 1; }
|
||||||
|
|
||||||
|
GITEA_HOST="${GITEA_URL#https://}"; GITEA_HOST="${GITEA_HOST#http://}"; GITEA_HOST="${GITEA_HOST%%/*}"
|
||||||
|
SETUP_URL="$GITEA_URL/$ORG/iride-setup.git"
|
||||||
|
APP_URL="$GITEA_URL/$ORG/iride.git"
|
||||||
|
|
||||||
|
echo "IRIDE bootstrap — $GITEA_URL/$ORG (app: $BRANCH, setup: $SETUP_BRANCH)"
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y -qq git curl ca-certificates >/dev/null
|
||||||
|
|
||||||
|
if ! id iride >/dev/null 2>&1; then
|
||||||
|
useradd --system --home-dir /opt/iride --shell /usr/sbin/nologin iride
|
||||||
|
fi
|
||||||
|
mkdir -p /opt/iride/config /opt/iride/setup
|
||||||
|
chown iride:iride /opt/iride /opt/iride/config /opt/iride/setup
|
||||||
|
chmod 700 /opt/iride/config
|
||||||
|
|
||||||
|
CRED="/opt/iride/config/git-credentials"
|
||||||
|
printf 'https://oauth2:%s@%s\n' "$TOKEN" "$GITEA_HOST" > "$CRED"
|
||||||
|
chown iride:iride "$CRED"; chmod 600 "$CRED"
|
||||||
|
sudo -u iride -H git config --global credential.helper "store --file=$CRED"
|
||||||
|
sudo -u iride -H git config --global safe.directory /opt/iride/setup
|
||||||
|
sudo -u iride -H git config --global safe.directory /opt/iride/app
|
||||||
|
|
||||||
|
if [[ -d /opt/iride/setup/.git ]]; then
|
||||||
|
sudo -u iride -H git -C /opt/iride/setup fetch --quiet origin "$SETUP_BRANCH"
|
||||||
|
sudo -u iride -H git -C /opt/iride/setup checkout --quiet "$SETUP_BRANCH"
|
||||||
|
sudo -u iride -H git -C /opt/iride/setup merge --ff-only --quiet "origin/$SETUP_BRANCH"
|
||||||
|
else
|
||||||
|
sudo -u iride -H git clone --quiet --branch "$SETUP_BRANCH" "$SETUP_URL" /opt/iride/setup
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec bash /opt/iride/setup/install.sh --app-url "$APP_URL" --branch "$BRANCH" --gitea-host "$GITEA_HOST" "${PASSTHROUGH[@]}"
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# IRIDE — verifica post-install / post-update (Tecnotel Servizi SRL)
|
||||||
|
# Uso: bash /opt/iride/setup/checks/health.sh (exit 1 se qualcosa non va)
|
||||||
|
set -uo pipefail
|
||||||
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
|
||||||
|
ok=0; ko=0
|
||||||
|
pass() { echo -e " ${GREEN}✔${NC} $1"; ok=$((ok+1)); }
|
||||||
|
fail() { echo -e " ${RED}✘${NC} $1"; ko=$((ko+1)); }
|
||||||
|
|
||||||
|
echo "IRIDE health check"
|
||||||
|
for svc in iride-api iride-worker iride-scheduler; do
|
||||||
|
systemctl is-active --quiet "$svc" && pass "$svc attivo" || fail "$svc non attivo (journalctl -u $svc -n 50)"
|
||||||
|
done
|
||||||
|
systemctl is-active --quiet nginx && pass "nginx attivo" || fail "nginx non attivo"
|
||||||
|
nginx -t >/dev/null 2>&1 && pass "configurazione nginx valida" || fail "nginx -t fallito"
|
||||||
|
|
||||||
|
for i in 1 2 3 4 5; do
|
||||||
|
HEALTH="$(curl -fsS http://127.0.0.1:8080/api/health 2>/dev/null)" && break
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
if [[ -n "${HEALTH:-}" ]]; then
|
||||||
|
pass "API: $(echo "$HEALTH" | python3 -c 'import json,sys; d=json.load(sys.stdin); print("versione", d["version"], "| schema", d["schema_version"], "| job", d["jobs"])' 2>/dev/null)"
|
||||||
|
else
|
||||||
|
fail "API non risponde su http://127.0.0.1:8080/api/health"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -f /opt/iride/data/iride.db ]] && pass "database presente" || fail "database assente in /opt/iride/data"
|
||||||
|
PERM="$(stat -c '%a %U' /opt/iride/config/iride.json 2>/dev/null)"
|
||||||
|
[[ "$PERM" == "600 iride" ]] && pass "config 0600 iride" || fail "permessi config: ${PERM:-assente} (atteso 600 iride)"
|
||||||
|
FREE_GB=$(df -BG --output=avail /opt/iride | tail -1 | tr -dc '0-9')
|
||||||
|
[[ "${FREE_GB:-0}" -ge 5 ]] && pass "spazio disco: ${FREE_GB} GB liberi" || echo -e " ${YELLOW}!${NC} spazio disco basso: ${FREE_GB:-?} GB"
|
||||||
|
|
||||||
|
echo " $ok ok, $ko errori"
|
||||||
|
[[ $ko -eq 0 ]]
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
IRIDE — first_setup.py: wizard di prima configurazione (CLI)
|
||||||
|
Tecnotel Servizi SRL
|
||||||
|
|
||||||
|
Eseguito da install.sh come utente iride con il venv dell'app e
|
||||||
|
PYTHONPATH=/opt/iride/app/backend. Scrive:
|
||||||
|
- $IRIDE_CONFIG_DIR/iride.json (cliente, dominio, chiavi di sistema generate)
|
||||||
|
- $IRIDE_CONFIG_DIR/users.json (utente admin con hash bcrypt)
|
||||||
|
|
||||||
|
Non interattivo con IRIDE_CLIENT_NAME, IRIDE_DOMAIN, IRIDE_ADMIN_USER,
|
||||||
|
IRIDE_ADMIN_PASSWORD nell'ambiente. Rieseguibile: non sovrascrive chiavi e
|
||||||
|
utenti già reali senza conferma. Il wizard web (porta 8888, stile ARGOS)
|
||||||
|
arriva dopo il pilota.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import getpass
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import secrets
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
CONFIG_DIR = Path(os.environ.get("IRIDE_CONFIG_DIR", "/opt/iride/config"))
|
||||||
|
PLACEHOLDER = "GENERATO-DA-first_setup"
|
||||||
|
INTERACTIVE = sys.stdin.isatty()
|
||||||
|
|
||||||
|
|
||||||
|
def ask(label: str, default: str = "", env: str = "", secret: bool = False, required: bool = False) -> str:
|
||||||
|
value = os.environ.get(env, "") if env else ""
|
||||||
|
if value:
|
||||||
|
return value
|
||||||
|
if not INTERACTIVE:
|
||||||
|
if required and not default:
|
||||||
|
sys.exit(f"Manca {env or label} (esecuzione non interattiva)")
|
||||||
|
return default
|
||||||
|
prompt = f"{label}" + (f" [{default}]" if default and not secret else "") + ": "
|
||||||
|
while True:
|
||||||
|
value = getpass.getpass(prompt) if secret else input(prompt)
|
||||||
|
value = value.strip() or default
|
||||||
|
if value or not required:
|
||||||
|
return value
|
||||||
|
print(" valore obbligatorio")
|
||||||
|
|
||||||
|
|
||||||
|
def slugify(s: str) -> str:
|
||||||
|
return re.sub(r"[^a-z0-9]+", "-", s.lower()).strip("-") or "cliente"
|
||||||
|
|
||||||
|
|
||||||
|
def load(path: Path) -> dict:
|
||||||
|
try:
|
||||||
|
return json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
except (OSError, ValueError):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def save(path: Path, data: dict) -> None:
|
||||||
|
tmp = path.with_suffix(".tmp")
|
||||||
|
tmp.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
||||||
|
os.chmod(tmp, 0o600)
|
||||||
|
tmp.replace(path)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
print("\n── IRIDE: prima configurazione ──")
|
||||||
|
iride = load(CONFIG_DIR / "iride.json")
|
||||||
|
users = load(CONFIG_DIR / "users.json")
|
||||||
|
cliente = iride.setdefault("cliente", {})
|
||||||
|
system = iride.setdefault("system", {})
|
||||||
|
|
||||||
|
already = system.get("secret_key") and system.get("secret_key") != PLACEHOLDER
|
||||||
|
if already and INTERACTIVE:
|
||||||
|
keep = ask("Configurazione già presente: mantenerla? (s/n)", "s")
|
||||||
|
if keep.lower().startswith("s"):
|
||||||
|
print("Configurazione mantenuta.")
|
||||||
|
return
|
||||||
|
|
||||||
|
full_name = ask("Nome del cliente", cliente.get("full_name") or "", "IRIDE_CLIENT_NAME", required=True)
|
||||||
|
cliente["full_name"] = full_name
|
||||||
|
cliente["name"] = slugify(cliente.get("name") if cliente.get("name") not in ("", "cliente") else full_name)
|
||||||
|
cliente["domain"] = ask("Dominio pubblico dell'istanza (FQDN)", cliente.get("domain") or "", "IRIDE_DOMAIN")
|
||||||
|
cliente.setdefault("type", "customer_service")
|
||||||
|
cliente.setdefault("ai_context", "")
|
||||||
|
system.setdefault("timezone", "Europe/Rome")
|
||||||
|
if not already:
|
||||||
|
system["secret_key"] = secrets.token_urlsafe(48)
|
||||||
|
system["internal_api_key"] = secrets.token_urlsafe(32)
|
||||||
|
ai = iride.setdefault("ai", {})
|
||||||
|
text = ai.setdefault("text", {"provider": "fake", "options": {}})
|
||||||
|
provider = ask("Provider AI per il testo (fake/openai)", text.get("provider", "fake"), "IRIDE_AI_PROVIDER")
|
||||||
|
text["provider"] = provider if provider in ("fake", "openai", "anthropic") else "fake"
|
||||||
|
if text["provider"] != "fake":
|
||||||
|
providers = ai.setdefault("providers", {})
|
||||||
|
key = ask(f"API key {text['provider']} (invio per lasciarla vuota)", "", "IRIDE_AI_API_KEY", secret=True)
|
||||||
|
if key:
|
||||||
|
providers.setdefault(text["provider"], {})["api_key"] = key
|
||||||
|
save(CONFIG_DIR / "iride.json", iride)
|
||||||
|
print(f" iride.json scritto (cliente: {cliente['name']}, dominio: {cliente['domain'] or '-'})")
|
||||||
|
|
||||||
|
from core.auth import hash_password # dal backend dell'app (PYTHONPATH)
|
||||||
|
admin_user = ask("Utente amministratore", "admin", "IRIDE_ADMIN_USER")
|
||||||
|
existing = (users.get("users") or {}).get(admin_user, {})
|
||||||
|
if existing.get("password_hash", "").startswith("$2") and PLACEHOLDER not in existing.get("password_hash", ""):
|
||||||
|
print(f" utente {admin_user} già presente: password invariata")
|
||||||
|
else:
|
||||||
|
while True:
|
||||||
|
pwd = ask(f"Password di {admin_user} (min 12 caratteri)", "", "IRIDE_ADMIN_PASSWORD", secret=True, required=True)
|
||||||
|
if len(pwd) < 12:
|
||||||
|
print(" troppo corta")
|
||||||
|
if not INTERACTIVE:
|
||||||
|
sys.exit("password troppo corta")
|
||||||
|
continue
|
||||||
|
if INTERACTIVE and not os.environ.get("IRIDE_ADMIN_PASSWORD"):
|
||||||
|
if getpass.getpass("Conferma password: ") != pwd:
|
||||||
|
print(" non coincidono")
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
users.setdefault("users", {})[admin_user] = {
|
||||||
|
"password_hash": hash_password(pwd), "roles": ["admin"], "totp_secret": "", "enabled": True}
|
||||||
|
save(CONFIG_DIR / "users.json", users)
|
||||||
|
print(f" users.json scritto (admin: {admin_user})")
|
||||||
|
print("── configurazione completata ──\n")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# IRIDE — install.sh: primo install di un'istanza (Tecnotel Servizi SRL)
|
||||||
|
# Lanciato da bootstrap.sh; rieseguibile (idempotente sui passi già fatti).
|
||||||
|
#
|
||||||
|
# sudo bash /opt/iride/setup/install.sh --app-url URL --branch main --gitea-host host
|
||||||
|
# [--client NOME] [--domain FQDN] [--admin-user admin] [--admin-password PWD]
|
||||||
|
#
|
||||||
|
# Passi: repo app → venv → config da esempi → wizard first_setup.py → migrazioni
|
||||||
|
# → build frontend (se presente) → systemd → nginx + certificato → permessi
|
||||||
|
# → avvio → health check. Gli aggiornamenti successivi: scripts/update.sh.
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
set -euo pipefail
|
||||||
|
SETUP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=lib/common.sh
|
||||||
|
source "$SETUP_DIR/lib/common.sh"
|
||||||
|
|
||||||
|
APP_URL=""; BRANCH="main"; GITEA_HOST=""
|
||||||
|
CLIENT=""; DOMAIN=""; ADMIN_USER="admin"; ADMIN_PASSWORD=""
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--app-url) APP_URL="$2"; shift 2 ;;
|
||||||
|
--branch) BRANCH="$2"; shift 2 ;;
|
||||||
|
--gitea-host) GITEA_HOST="$2"; shift 2 ;;
|
||||||
|
--client) CLIENT="$2"; shift 2 ;;
|
||||||
|
--domain) DOMAIN="$2"; shift 2 ;;
|
||||||
|
--admin-user) ADMIN_USER="$2"; shift 2 ;;
|
||||||
|
--admin-password) ADMIN_PASSWORD="$2"; shift 2 ;;
|
||||||
|
*) error "Opzione sconosciuta: $1" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
require_root
|
||||||
|
[[ -n "$APP_URL" ]] || APP_URL="https://${GITEA_HOST:-repo.argosdefense.io}/tecnotel/iride.git"
|
||||||
|
|
||||||
|
section "1. Sistema"
|
||||||
|
detect_os
|
||||||
|
ensure_packages
|
||||||
|
ensure_user_and_dirs
|
||||||
|
|
||||||
|
section "2. Repository applicativo ($BRANCH)"
|
||||||
|
clone_or_update "$APP_URL" "$IRIDE_APP" "$BRANCH"
|
||||||
|
APP_VERSION="$(cat "$IRIDE_APP/VERSION" 2>/dev/null || echo '?')"
|
||||||
|
info "IRIDE $APP_VERSION"
|
||||||
|
|
||||||
|
section "3. Virtualenv e dipendenze"
|
||||||
|
if [[ ! -x "$IRIDE_VENV/bin/python" ]]; then
|
||||||
|
as_iride python3 -m venv "$IRIDE_VENV"
|
||||||
|
fi
|
||||||
|
as_iride "$IRIDE_VENV/bin/pip" install --quiet --upgrade pip
|
||||||
|
as_iride "$IRIDE_VENV/bin/pip" install --quiet -r "$IRIDE_APP/backend/requirements.txt"
|
||||||
|
success "Dipendenze installate in $IRIDE_VENV"
|
||||||
|
|
||||||
|
section "4. Configurazione"
|
||||||
|
for example in "$IRIDE_APP"/config/*.example; do
|
||||||
|
target="$IRIDE_CONFIG/$(basename "${example%.example}")"
|
||||||
|
if [[ ! -f "$target" ]]; then
|
||||||
|
cp "$example" "$target"
|
||||||
|
info "Creato $target da esempio"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
chown "$IRIDE_USER:$IRIDE_USER" "$IRIDE_CONFIG"/*.json
|
||||||
|
chmod 600 "$IRIDE_CONFIG"/*.json
|
||||||
|
export IRIDE_CONFIG_DIR="$IRIDE_CONFIG" IRIDE_DATA_DIR="$IRIDE_DATA" IRIDE_LOGS_DIR="$IRIDE_LOGS" IRIDE_DB="$IRIDE_DATA/iride.db"
|
||||||
|
export IRIDE_CLIENT_NAME="$CLIENT" IRIDE_DOMAIN="$DOMAIN" IRIDE_ADMIN_USER="$ADMIN_USER" IRIDE_ADMIN_PASSWORD="$ADMIN_PASSWORD"
|
||||||
|
sudo -u "$IRIDE_USER" -H --preserve-env=IRIDE_CONFIG_DIR,IRIDE_DATA_DIR,IRIDE_LOGS_DIR,IRIDE_DB,IRIDE_CLIENT_NAME,IRIDE_DOMAIN,IRIDE_ADMIN_USER,IRIDE_ADMIN_PASSWORD \
|
||||||
|
env PYTHONPATH="$IRIDE_APP/backend" "$IRIDE_VENV/bin/python" "$SETUP_DIR/first_setup.py"
|
||||||
|
SERVER_NAME="$("$IRIDE_VENV/bin/python" -c "import json; print(json.load(open('$IRIDE_CONFIG/iride.json'))['cliente'].get('domain') or '')")"
|
||||||
|
[[ -n "$SERVER_NAME" ]] || SERVER_NAME="$(hostname -f 2>/dev/null || hostname)"
|
||||||
|
|
||||||
|
section "5. Migrazioni DB"
|
||||||
|
cd "$IRIDE_APP/backend"
|
||||||
|
sudo -u "$IRIDE_USER" -H --preserve-env=IRIDE_CONFIG_DIR,IRIDE_DATA_DIR,IRIDE_LOGS_DIR,IRIDE_DB "$IRIDE_VENV/bin/python" db.py
|
||||||
|
success "Schema allineato"
|
||||||
|
|
||||||
|
section "6. Frontend"
|
||||||
|
if [[ -f "$IRIDE_APP/frontend/package.json" ]]; then
|
||||||
|
command -v npm >/dev/null || error "npm assente: installare Node LTS (NodeSource) e rilanciare"
|
||||||
|
cd "$IRIDE_APP/frontend"
|
||||||
|
as_iride npm ci --silent
|
||||||
|
as_iride npm run build
|
||||||
|
success "Frontend compilato"
|
||||||
|
else
|
||||||
|
warn "Nessun frontend/package.json: nginx servirà solo l'API (B-067)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
section "7. systemd"
|
||||||
|
for svc in "${IRIDE_SERVICES[@]}"; do
|
||||||
|
cp "$IRIDE_APP/deploy/systemd/$svc.service" "/etc/systemd/system/$svc.service"
|
||||||
|
done
|
||||||
|
systemctl daemon-reload
|
||||||
|
for svc in "${IRIDE_SERVICES[@]}"; do systemctl enable --quiet "$svc"; done
|
||||||
|
cp "$IRIDE_APP/deploy/logrotate/iride" /etc/logrotate.d/iride
|
||||||
|
cp "$IRIDE_APP/deploy/sudoers/iride-systemctl" /etc/sudoers.d/iride-systemctl
|
||||||
|
chmod 440 /etc/sudoers.d/iride-systemctl
|
||||||
|
visudo -cf /etc/sudoers.d/iride-systemctl >/dev/null || error "sudoers non valido"
|
||||||
|
success "Unit installate e abilitate: ${IRIDE_SERVICES[*]}"
|
||||||
|
|
||||||
|
section "8. nginx e certificato ($SERVER_NAME)"
|
||||||
|
if [[ ! -f "$IRIDE_CERTS/fullchain.pem" ]]; then
|
||||||
|
openssl req -x509 -nodes -newkey rsa:2048 -days 825 -subj "/CN=$SERVER_NAME" \
|
||||||
|
-keyout "$IRIDE_CERTS/privkey.pem" -out "$IRIDE_CERTS/fullchain.pem" >/dev/null 2>&1
|
||||||
|
chown "$IRIDE_USER:$IRIDE_USER" "$IRIDE_CERTS"/*.pem; chmod 600 "$IRIDE_CERTS/privkey.pem"
|
||||||
|
warn "Certificato self-signed generato: per WhatsApp serve un certificato valido in $IRIDE_CERTS"
|
||||||
|
fi
|
||||||
|
sed "s/IRIDE_SERVER_NAME/$SERVER_NAME/g" "$IRIDE_APP/deploy/nginx/iride.conf" > /etc/nginx/sites-available/iride
|
||||||
|
ln -sf /etc/nginx/sites-available/iride /etc/nginx/sites-enabled/iride
|
||||||
|
rm -f /etc/nginx/sites-enabled/default
|
||||||
|
nginx -t >/dev/null 2>&1 || error "Configurazione nginx non valida: nginx -t"
|
||||||
|
systemctl enable --quiet nginx
|
||||||
|
systemctl reload nginx || systemctl restart nginx
|
||||||
|
success "nginx configurato"
|
||||||
|
|
||||||
|
section "9. Permessi"
|
||||||
|
chown -R "$IRIDE_USER:$IRIDE_USER" "$IRIDE_ROOT"
|
||||||
|
chmod 700 "$IRIDE_CONFIG"
|
||||||
|
chmod 600 "$IRIDE_CONFIG"/*.json "$IRIDE_CONFIG/git-credentials" 2>/dev/null || true
|
||||||
|
success "Proprietario $IRIDE_USER, config 0600"
|
||||||
|
|
||||||
|
section "10. Avvio e verifica"
|
||||||
|
for svc in "${IRIDE_SERVICES[@]}"; do systemctl restart "$svc"; done
|
||||||
|
sleep 2
|
||||||
|
bash "$SETUP_DIR/checks/health.sh" || error "Health check fallito: vedere $IRIDE_LOGS"
|
||||||
|
|
||||||
|
echo
|
||||||
|
success "IRIDE $APP_VERSION installato"
|
||||||
|
echo " Portale: https://$SERVER_NAME/ (API: /api/health, /api/docs)"
|
||||||
|
echo " Utente admin: $ADMIN_USER"
|
||||||
|
echo " Prossimi passi: canali in $IRIDE_CONFIG/channels.json e credenziali in credentials.json,"
|
||||||
|
echo " poi: sudo systemctl restart iride-api"
|
||||||
|
echo " Aggiornamenti: sudo bash $IRIDE_APP/scripts/update.sh"
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# IRIDE setup — funzioni comuni (sourced da bootstrap.sh e install.sh)
|
||||||
|
# Tecnotel Servizi SRL
|
||||||
|
|
||||||
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; CYAN='\033[0;36m'; NC='\033[0m'
|
||||||
|
info() { echo -e "${CYAN}[INFO]${NC} $1"; }
|
||||||
|
success() { echo -e "${GREEN}[OK]${NC} $1"; }
|
||||||
|
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||||
|
error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
|
||||||
|
section() { echo -e "\n${BLUE}══════════════════════════════════════${NC}"; echo -e "${BLUE} $1${NC}"; echo -e "${BLUE}══════════════════════════════════════${NC}"; }
|
||||||
|
|
||||||
|
IRIDE_ROOT="/opt/iride"
|
||||||
|
IRIDE_APP="$IRIDE_ROOT/app"
|
||||||
|
IRIDE_CONFIG="$IRIDE_ROOT/config"
|
||||||
|
IRIDE_DATA="$IRIDE_ROOT/data"
|
||||||
|
IRIDE_LOGS="$IRIDE_ROOT/logs"
|
||||||
|
IRIDE_BACKUPS="$IRIDE_ROOT/backups"
|
||||||
|
IRIDE_CERTS="$IRIDE_ROOT/certs"
|
||||||
|
IRIDE_SETUP="$IRIDE_ROOT/setup"
|
||||||
|
IRIDE_USER="iride"
|
||||||
|
IRIDE_VENV="$IRIDE_APP/backend/venv"
|
||||||
|
IRIDE_SERVICES=(iride-api iride-worker iride-scheduler)
|
||||||
|
|
||||||
|
require_root() { [[ $EUID -eq 0 ]] || error "Eseguire con sudo"; }
|
||||||
|
|
||||||
|
detect_os() {
|
||||||
|
if [[ -f /etc/os-release ]]; then
|
||||||
|
. /etc/os-release
|
||||||
|
case "${ID:-}:${VERSION_ID:-}" in
|
||||||
|
ubuntu:22.04|ubuntu:24.04) success "Sistema: $PRETTY_NAME" ;;
|
||||||
|
ubuntu:*) warn "Ubuntu ${VERSION_ID} non collaudato (riferimento: 22.04 / 24.04)" ;;
|
||||||
|
*) warn "Distribuzione ${ID:-?} non collaudata: il riferimento è Ubuntu Server LTS" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
as_iride() { sudo -u "$IRIDE_USER" -H "$@"; }
|
||||||
|
|
||||||
|
ensure_packages() {
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y -qq git curl ca-certificates openssl python3 python3-venv python3-pip nginx sqlite3 >/dev/null
|
||||||
|
success "Pacchetti di base installati"
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_user_and_dirs() {
|
||||||
|
if ! id "$IRIDE_USER" >/dev/null 2>&1; then
|
||||||
|
useradd --system --home-dir "$IRIDE_ROOT" --shell /usr/sbin/nologin "$IRIDE_USER"
|
||||||
|
success "Utente di servizio $IRIDE_USER creato"
|
||||||
|
fi
|
||||||
|
mkdir -p "$IRIDE_APP" "$IRIDE_CONFIG" "$IRIDE_DATA" "$IRIDE_LOGS" "$IRIDE_BACKUPS" "$IRIDE_CERTS" "$IRIDE_SETUP"
|
||||||
|
chown "$IRIDE_USER:$IRIDE_USER" "$IRIDE_ROOT" "$IRIDE_APP" "$IRIDE_CONFIG" "$IRIDE_DATA" "$IRIDE_LOGS" "$IRIDE_BACKUPS" "$IRIDE_CERTS" "$IRIDE_SETUP"
|
||||||
|
chmod 750 "$IRIDE_ROOT"
|
||||||
|
chmod 700 "$IRIDE_CONFIG"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Credenziali git di sola lettura per le istanze: file 0600 dell'utente iride,
|
||||||
|
# usato dal credential helper "store". Mai token personali.
|
||||||
|
configure_git_credentials() {
|
||||||
|
local gitea_host="$1" token="$2"
|
||||||
|
local cred="$IRIDE_CONFIG/git-credentials"
|
||||||
|
printf 'https://oauth2:%s@%s\n' "$token" "$gitea_host" > "$cred"
|
||||||
|
chown "$IRIDE_USER:$IRIDE_USER" "$cred"; chmod 600 "$cred"
|
||||||
|
as_iride git config --global credential.helper "store --file=$cred"
|
||||||
|
as_iride git config --global safe.directory "$IRIDE_APP"
|
||||||
|
as_iride git config --global safe.directory "$IRIDE_SETUP"
|
||||||
|
success "Credenziali git salvate in $cred (0600)"
|
||||||
|
}
|
||||||
|
|
||||||
|
clone_or_update() {
|
||||||
|
local url="$1" dest="$2" branch="$3"
|
||||||
|
if [[ -d "$dest/.git" ]]; then
|
||||||
|
as_iride git -C "$dest" fetch --quiet origin "$branch"
|
||||||
|
as_iride git -C "$dest" checkout --quiet "$branch"
|
||||||
|
as_iride git -C "$dest" merge --ff-only --quiet "origin/$branch"
|
||||||
|
success "Aggiornato $dest ($branch)"
|
||||||
|
else
|
||||||
|
as_iride git clone --quiet --branch "$branch" "$url" "$dest"
|
||||||
|
success "Clonato $url in $dest"
|
||||||
|
fi
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue