Pacchetto Windows: STATION_CODE predefinito e chiavi extra da config.env, alembic.ini sourceless, fileConfig neutralizzato (TieMeasureFlow#76)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -59,6 +59,8 @@ COMPILATO = "__compiled__" in globals()
|
|||||||
RADICE_SORGENTI = Path(__file__).resolve().parents[2]
|
RADICE_SORGENTI = Path(__file__).resolve().parents[2]
|
||||||
# Segreti generati al primo avvio e riletti dopo (nome -> generatore)
|
# Segreti generati al primo avvio e riletti dopo (nome -> generatore)
|
||||||
SEGRETI = ("SERVER_SECRET_KEY", "CLIENT_SECRET_KEY", "SETUP_PASSWORD", "DB_PASSWORD")
|
SEGRETI = ("SERVER_SECRET_KEY", "CLIENT_SECRET_KEY", "SETUP_PASSWORD", "DB_PASSWORD")
|
||||||
|
# STATION_CODE: senza una stazione l'interfaccia risponde 503; ST-DEFAULT e' quella creata dal seed della pagina di setup
|
||||||
|
PREDEFINITI = {"STATION_CODE": "ST-DEFAULT"}
|
||||||
|
|
||||||
|
|
||||||
def _stream_sicuri() -> None:
|
def _stream_sicuri() -> None:
|
||||||
@@ -155,9 +157,13 @@ def carica_segreti(dati: Path) -> dict[str, str]:
|
|||||||
percorso = dati / "config.env"
|
percorso = dati / "config.env"
|
||||||
valori = _leggi_env(percorso) if percorso.exists() else {}
|
valori = _leggi_env(percorso) if percorso.exists() else {}
|
||||||
nuovi = [k for k in SEGRETI if not valori.get(k)]
|
nuovi = [k for k in SEGRETI if not valori.get(k)]
|
||||||
if nuovi:
|
# valori di partenza modificabili dall'utente in config.env (scritti una volta sola)
|
||||||
|
mancanti = [k for k in PREDEFINITI if k not in valori]
|
||||||
|
if nuovi or mancanti:
|
||||||
for k in nuovi:
|
for k in nuovi:
|
||||||
valori[k] = secrets.token_urlsafe(32)
|
valori[k] = secrets.token_urlsafe(32)
|
||||||
|
for k in mancanti:
|
||||||
|
valori[k] = PREDEFINITI[k]
|
||||||
righe = ["# Generato da TieMeasureFlow al primo avvio. Non condividere questo file.",
|
righe = ["# Generato da TieMeasureFlow al primo avvio. Non condividere questo file.",
|
||||||
"# SETUP_PASSWORD serve per la pagina http://127.0.0.1:8000/api/setup (primo amministratore)."]
|
"# SETUP_PASSWORD serve per la pagina http://127.0.0.1:8000/api/setup (primo amministratore)."]
|
||||||
righe += [f"{k}={v}" for k, v in valori.items()]
|
righe += [f"{k}={v}" for k, v in valori.items()]
|
||||||
@@ -199,6 +205,10 @@ def imposta_ambiente(dati: Path, host: str, valori: dict[str, str]) -> None:
|
|||||||
"SERVER_CORS_ORIGINS": f"http://127.0.0.1:{PORTA_WEB},http://localhost:{PORTA_WEB}",
|
"SERVER_CORS_ORIGINS": f"http://127.0.0.1:{PORTA_WEB},http://localhost:{PORTA_WEB}",
|
||||||
**{k: valori[k] for k in SEGRETI},
|
**{k: valori[k] for k in SEGRETI},
|
||||||
}
|
}
|
||||||
|
# le altre chiavi di config.env (STATION_CODE, STATION_SWITCH_ENABLED, ...) passano cosi' come sono,
|
||||||
|
# ma non sovrascrivono le variabili fisse del contratto
|
||||||
|
for k, v in valori.items():
|
||||||
|
env.setdefault(k, v)
|
||||||
programma = cartella_programma()
|
programma = cartella_programma()
|
||||||
versione = programma / "vision-engine-version.txt" # scritto da build.ps1
|
versione = programma / "vision-engine-version.txt" # scritto da build.ps1
|
||||||
if versione.is_file():
|
if versione.is_file():
|
||||||
@@ -296,8 +306,16 @@ def _apri_browser_quando_pronto(url: str, porte: tuple[int, ...]) -> None:
|
|||||||
|
|
||||||
# ---------------------------------------------------------------- migrazioni
|
# ---------------------------------------------------------------- migrazioni
|
||||||
|
|
||||||
|
def _neutralizza_fileconfig() -> None:
|
||||||
|
# env.py di Alembic chiama logging.config.fileConfig (anche la pagina di setup, con command.stamp):
|
||||||
|
# azzererebbe i gestori del log su file. Nel pacchetto il log lo decide avvio.py.
|
||||||
|
import logging.config
|
||||||
|
logging.config.fileConfig = lambda *a, **k: None
|
||||||
|
|
||||||
|
|
||||||
def esegui_migrazioni() -> None:
|
def esegui_migrazioni() -> None:
|
||||||
"""alembic upgrade head. Compilato: env.py e versions\\ sono soli .pyc, quindi sourceless."""
|
"""alembic upgrade head. Compilato: env.py e versions\\ sono soli .pyc, quindi sourceless."""
|
||||||
|
_neutralizza_fileconfig()
|
||||||
from alembic import command
|
from alembic import command
|
||||||
from alembic.config import Config
|
from alembic.config import Config
|
||||||
import src.backend as pacchetto
|
import src.backend as pacchetto
|
||||||
|
|||||||
@@ -174,6 +174,14 @@ if ($LASTEXITCODE -ne 0) { throw "compileall delle migrazioni fallito" }
|
|||||||
Get-ChildItem $mig -Recurse -Filter *.py -File | Remove-Item -Force
|
Get-ChildItem $mig -Recurse -Filter *.py -File | Remove-Item -Force
|
||||||
Get-ChildItem $mig -Recurse -Directory -Filter __pycache__ | Remove-Item -Recurse -Force
|
Get-ChildItem $mig -Recurse -Directory -Filter __pycache__ | Remove-Item -Recurse -Force
|
||||||
|
|
||||||
|
# alembic.ini del pacchetto: sourceless, perche' anche la pagina di setup (command.stamp) apre
|
||||||
|
# alembic.ini con un Config suo e non troverebbe env.py/versions (solo .pyc)
|
||||||
|
$ini = Join-Path $mig 'alembic.ini'
|
||||||
|
$testoIni = [System.IO.File]::ReadAllText($ini)
|
||||||
|
if ($testoIni -notmatch '(?m)^script_location') { throw "alembic.ini senza script_location" }
|
||||||
|
$testoIni = $testoIni -replace '(?m)^(script_location[^\r\n]*)', "`$1`nsourceless = true"
|
||||||
|
[System.IO.File]::WriteAllText($ini, $testoIni, (New-Object System.Text.UTF8Encoding $false))
|
||||||
|
|
||||||
# Commit del motore di visione: avvio.py lo legge in VISION_ENGINE_VERSION
|
# Commit del motore di visione: avvio.py lo legge in VISION_ENGINE_VERSION
|
||||||
$motore = (git -C vendor/visionsuite rev-parse HEAD).Trim()
|
$motore = (git -C vendor/visionsuite rev-parse HEAD).Trim()
|
||||||
if ($LASTEXITCODE -ne 0 -or -not $motore) { throw "Commit di vendor/visionsuite non leggibile" }
|
if ($LASTEXITCODE -ne 0 -or -not $motore) { throw "Commit di vendor/visionsuite non leggibile" }
|
||||||
|
|||||||
@@ -126,8 +126,9 @@ def test_config_env_generato_una_volta_e_riletto(avvio, tmp_path):
|
|||||||
dati = tmp_path / "Tielogic" / "TieMeasureFlow"
|
dati = tmp_path / "Tielogic" / "TieMeasureFlow"
|
||||||
primo = avvio.carica_segreti(dati)
|
primo = avvio.carica_segreti(dati)
|
||||||
testo = (dati / "config.env").read_text(encoding="utf-8")
|
testo = (dati / "config.env").read_text(encoding="utf-8")
|
||||||
assert set(primo) == {"SERVER_SECRET_KEY", "CLIENT_SECRET_KEY", "SETUP_PASSWORD", "DB_PASSWORD"}
|
assert set(primo) == {"SERVER_SECRET_KEY", "CLIENT_SECRET_KEY", "SETUP_PASSWORD", "DB_PASSWORD", "STATION_CODE"}
|
||||||
assert len(set(primo.values())) == 4 # segreti distinti
|
assert len({primo[k] for k in avvio.SEGRETI}) == 4 # segreti distinti
|
||||||
|
assert primo["STATION_CODE"] == "ST-DEFAULT"
|
||||||
secondo = avvio.carica_segreti(dati)
|
secondo = avvio.carica_segreti(dati)
|
||||||
assert secondo == primo and (dati / "config.env").read_text(encoding="utf-8") == testo
|
assert secondo == primo and (dati / "config.env").read_text(encoding="utf-8") == testo
|
||||||
|
|
||||||
@@ -184,3 +185,13 @@ def test_nessun_utente_rileva_il_primo_avvio(avvio, tmp_path):
|
|||||||
con.commit()
|
con.commit()
|
||||||
con.close()
|
con.close()
|
||||||
assert avvio._nessun_utente(db) is False
|
assert avvio._nessun_utente(db) is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_chiavi_extra_di_config_env_passano_ma_non_sovrascrivono_il_contratto(avvio, tmp_path):
|
||||||
|
_scrivi_licenza(tmp_path)
|
||||||
|
dati = tmp_path / "Tielogic" / "TieMeasureFlow"
|
||||||
|
dati.mkdir(parents=True)
|
||||||
|
(dati / "config.env").write_text("STATION_CODE=ST-FIERA\nSTATION_SWITCH_ENABLED=1\nAPP_ENV=dev\n", encoding="utf-8")
|
||||||
|
avvio.main(["--senza-finestre", "--senza-browser"])
|
||||||
|
assert os.environ["STATION_CODE"] == "ST-FIERA" and os.environ["STATION_SWITCH_ENABLED"] == "1"
|
||||||
|
assert os.environ["APP_ENV"] == "production" # le variabili fisse vincono
|
||||||
|
|||||||
Reference in New Issue
Block a user