From eea3e319fb406d93a5d879e488a9e36e52c361e8 Mon Sep 17 00:00:00 2001 From: "Adriano (daitarn)" Date: Sat, 17 Oct 2026 10:55:48 +0200 Subject: [PATCH] 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 --- packaging/windows/avvio.py | 20 +++++++++++++++++++- packaging/windows/build.ps1 | 8 ++++++++ packaging/windows/tests/test_avvio.py | 15 +++++++++++++-- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/packaging/windows/avvio.py b/packaging/windows/avvio.py index 1817ac0..b2263d5 100644 --- a/packaging/windows/avvio.py +++ b/packaging/windows/avvio.py @@ -59,6 +59,8 @@ COMPILATO = "__compiled__" in globals() RADICE_SORGENTI = Path(__file__).resolve().parents[2] # Segreti generati al primo avvio e riletti dopo (nome -> generatore) 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: @@ -155,9 +157,13 @@ def carica_segreti(dati: Path) -> dict[str, str]: percorso = dati / "config.env" valori = _leggi_env(percorso) if percorso.exists() else {} 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: 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.", "# 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()] @@ -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}", **{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() versione = programma / "vision-engine-version.txt" # scritto da build.ps1 if versione.is_file(): @@ -296,8 +306,16 @@ def _apri_browser_quando_pronto(url: str, porte: tuple[int, ...]) -> None: # ---------------------------------------------------------------- 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: """alembic upgrade head. Compilato: env.py e versions\\ sono soli .pyc, quindi sourceless.""" + _neutralizza_fileconfig() from alembic import command from alembic.config import Config import src.backend as pacchetto diff --git a/packaging/windows/build.ps1 b/packaging/windows/build.ps1 index 7524d85..074039c 100644 --- a/packaging/windows/build.ps1 +++ b/packaging/windows/build.ps1 @@ -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 -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 $motore = (git -C vendor/visionsuite rev-parse HEAD).Trim() if ($LASTEXITCODE -ne 0 -or -not $motore) { throw "Commit di vendor/visionsuite non leggibile" } diff --git a/packaging/windows/tests/test_avvio.py b/packaging/windows/tests/test_avvio.py index 3eec8cd..04226e9 100644 --- a/packaging/windows/tests/test_avvio.py +++ b/packaging/windows/tests/test_avvio.py @@ -126,8 +126,9 @@ def test_config_env_generato_una_volta_e_riletto(avvio, tmp_path): dati = tmp_path / "Tielogic" / "TieMeasureFlow" primo = avvio.carica_segreti(dati) testo = (dati / "config.env").read_text(encoding="utf-8") - assert set(primo) == {"SERVER_SECRET_KEY", "CLIENT_SECRET_KEY", "SETUP_PASSWORD", "DB_PASSWORD"} - assert len(set(primo.values())) == 4 # segreti distinti + assert set(primo) == {"SERVER_SECRET_KEY", "CLIENT_SECRET_KEY", "SETUP_PASSWORD", "DB_PASSWORD", "STATION_CODE"} + assert len({primo[k] for k in avvio.SEGRETI}) == 4 # segreti distinti + assert primo["STATION_CODE"] == "ST-DEFAULT" secondo = avvio.carica_segreti(dati) 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.close() 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