Le téléphone balayait quarante radios toutes les trois minutes, cherchait dans quatre catalogues et enregistrait des directs. Tout cela le tenait éveillé pour un travail qui n'a aucune raison de se faire sur batterie. Un serveur reprend la collecte : recherche, guet, enregistrement, file de téléchargement, piloté depuis un navigateur. Le mobile demande ce qui a changé depuis son curseur, télécharge et se rendort — toutes les six heures, en Wi-Fi non facturé. Vingt fichiers quittent l'application au passage. Le balayage devient continu plutôt qu'espacé de trois minutes : une sonde ne lit que le premier bloc de métadonnées avant de refermer, ce qui rend la permanence tenable quand garder les flux ouverts coûterait plusieurs mégabits par seconde. Un morceau guetté a été capté en moins de vingt-cinq secondes. La recherche interroge les quatre catalogues à la fois et fond leurs résultats, les plus légers d'abord. Quatre défauts corrigés en chemin, tous trouvés en essayant plutôt qu'en relisant : un jeton régénéré à chaque démarrage, qui aurait obligé à reconfigurer le mobile ; un 403 de Wikimedia dû à un agent trop générique, que la fusion des sources masquait ; un dossier de données par défaut inaccessible au compte qui lance le serveur ; et une lecture de métadonnées qui n'itérait rien, faute d'être asynchrone.
68 lines
2.3 KiB
Python
68 lines
2.3 KiB
Python
"""Configuration du serveur Noxely, lue de l'environnement.
|
|
|
|
Aucune valeur sensible n'est écrite dans le dépôt : le jeton et les chemins viennent d'un
|
|
fichier .env local, à l'image des autres services de la maison.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import secrets
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
|
|
def _persisted_token(data_dir: Path) -> str:
|
|
"""Jeton auto-généré, conservé entre deux démarrages.
|
|
|
|
Un jeton tiré à chaque lancement obligerait à reconfigurer le téléphone après le moindre
|
|
redémarrage : il est donc écrit une fois, en lecture seule pour son propriétaire.
|
|
"""
|
|
path = data_dir / "token"
|
|
if path.exists():
|
|
existing = path.read_text().strip()
|
|
if existing:
|
|
return existing
|
|
token = secrets.token_urlsafe(24)
|
|
data_dir.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(token + "\n")
|
|
path.chmod(0o600)
|
|
print(f"[noxely] jeton généré et conservé dans {path}")
|
|
return token
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Config:
|
|
data_dir: Path
|
|
token: str
|
|
host: str
|
|
port: int
|
|
#: Nombre de stations balayées à chaque passage du guet.
|
|
watch_stations: int
|
|
#: Pause entre deux balayages, en secondes. Le balayage est continu : cette pause n'est
|
|
#: qu'un garde-fou pour ne pas marteler l'annuaire si un tour échoue instantanément.
|
|
watch_interval: int
|
|
#: Stations sondées en parallèle. Plus il est haut, plus un tour complet est bref.
|
|
watch_concurrency: int
|
|
|
|
@property
|
|
def media_dir(self) -> Path:
|
|
return self.data_dir / "media"
|
|
|
|
@property
|
|
def db_path(self) -> Path:
|
|
return self.data_dir / "noxely.sqlite3"
|
|
|
|
|
|
def load() -> Config:
|
|
data_dir = Path(os.environ.get("NOXELY_DATA", "./data")).expanduser().resolve()
|
|
token = os.environ.get("NOXELY_TOKEN", "").strip() or _persisted_token(data_dir)
|
|
return Config(
|
|
data_dir=data_dir,
|
|
token=token,
|
|
host=os.environ.get("NOXELY_HOST", "0.0.0.0"),
|
|
port=int(os.environ.get("NOXELY_PORT", "8710")),
|
|
watch_stations=int(os.environ.get("NOXELY_WATCH_STATIONS", "40")),
|
|
watch_interval=int(os.environ.get("NOXELY_WATCH_INTERVAL", "5")),
|
|
watch_concurrency=int(os.environ.get("NOXELY_WATCH_CONCURRENCY", "8")),
|
|
)
|