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]
|
||||
# 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
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user