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:
2026-10-17 10:55:48 +02:00
parent bfc7c391e8
commit eea3e319fb
3 changed files with 40 additions and 3 deletions
+19 -1
View File
@@ -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
+8
View File
@@ -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" }
+13 -2
View File
@@ -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