rescriptum · an answer written for this machine
FR
Sur cette page

Architecture

Un processus, une crate, aucun framework. main.rs est un binaire mince par-dessus lib.rs, pour que chaque comportement soit testable directement plutôt qu’à travers une socket.

La forme de l’ensemble

flowchart TB
  subgraph net["Réseau"]
    I["Installateur<br/>POST /answer · GET /rhel/ks"]
    A["Client d'administration"]
  end

  I --> M["main.rs<br/>accept · timeouts · routage"]
  A --> AD["admin.rs<br/>listener propre · auth · écritures gardées"]

  M --> F["facts.rs<br/>query · feuilles JSON · botte de foin"]
  F --> S["select.rs<br/>match · superposer · remplir"]
  AD --> S

  S --> FM["format/<br/>parser · fusionner · rendre"]
  FM --> MG["merge.rs<br/>fusion profonde TOML"]
  FM --> X["format/xml.rs<br/>arbre XML"]

  S --> ST["store/ (trait)"]
  AD --> ST
  ST --> FS["file.rs<br/>un répertoire"]
  ST --> SQ["sqlite.rs<br/>une base"]

  CLI["cli.rs<br/>render · check · import · export"] --> S

Ce que possède chaque pièce

ModulePossède
main.rsle runtime tokio, la boucle d’accept, le sémaphore de connexions, les deux timeouts, le routage, la vérification du jeton de réponse, et l’appel spawn_blocking qui fait la recherche
facts.rstransformer une requête en valeurs étiquetées — paramètres de query, corps JSON aplati, segments de chemin, et la botte de foin normalisée
select.rsle comportement qui compte : normalisation, scoring, chaîne de groupes, ordre de fusion, remplissage de templates, et le listing mis en cache
format/une interface par format de document. Doc parse, fusionne, rend, et signale ses clés de contrôle
merge.rsla fusion profonde TOML, utilisée par format
store/d’où viennent les documents, derrière un trait de lecture à deux méthodes
admin.rsson propre listener, l’auth bearer, le garde-fou d’échecs, et l’annulation qui empêche une écriture de casser le jeu de réponses
config.rsl’environnement, et la validation qui transforme une configuration dangereuse en erreur de démarrage
envfile.rsle fichier que nomme RESCRIPTUM_ENV_FILE : parsé, jamais découvert, et fatal s’il est illisible
cli.rsrender, check, import, export
capture.rsl’enregistrement des corps de requête
log.rsune ligne par événement, des horodatages UTC calculés sans crate de date, et les deux réglages au-dessus : ce qui est gardé, et où cela va

La seule frontière qui mérite d’être défendue

Le store est délibérément mince. Il rend le texte brut des documents et un jeton de version bon marché, et ne décide de rien :

pub trait Store: Send + Sync {
    fn version(&self) -> Version;               // assez peu cher pour être appelé par requête
    fn snapshot(&self) -> io::Result<Snapshot>; // seulement quand la version a bougé
    fn describe(&self) -> String;
}

Chaque décision — correspondance, chaînes extends, fusion, rendu, check — vit au-dessus, dans select.rs et merge.rs, et est partagée par les deux backends. Dès qu’un backend se met à décider du comportement, les deux divergent.

tests/stores.rs est ce qui fait de cela une garantie plutôt qu’une intention : chaque cas de comportement tourne deux fois, une par store, et affirme le résultat identique.

La moitié écriture est un trait séparé, parce que servir des réponses n’en a jamais besoin :

pub trait StoreWrite: Store {
    fn put_machine(&self, id: &str, format: &str, body: &str) -> io::Result<()>;
    fn delete_machine(&self, id: &str, format: &str) -> io::Result<bool>;
    // …groupes, défaut
}

Notez que chaque opération nomme un format. Un document est indexé par ce à quoi il sert — une machine et un système d’exploitation — pas par identifiant seul.

La couche de cache

Answers enveloppe un store et garde un Listing parsé et fusionné derrière un mutex :

struct Cached { version: Version, loaded_at: Instant, listing: Arc<Listing> }

Une requête réutilise le cache seulement quand les trois conditions tiennent :

  1. store.version() est inchangée — pour les fichiers, le mtime du répertoire ; pour SQLite, un atomique en mémoire ;
  2. cette version est Some — une version illisible n’est jamais traitée comme « inchangée » ;
  3. moins de RELOAD_BACKSTOP (1 s) s’est écoulé.

Le filet n’est pas redondant avec la vérification de version. Éditer le contenu d’un fichier de groupe ne bouge aucun mtime de répertoire, et un changement fait par un autre processus ne bouge aucun atomique en mémoire. Sans le filet, l’une ou l’autre édition serait invisible jusqu’à ce qu’autre chose arrive au répertoire.

Un mutex empoisonné — une autre requête a paniqué en plein rafraîchissement — est récupéré plutôt que propagé. Les données en cache sont encore structurellement saines, et faire échouer une installation à cause du panic d’une autre requête serait le mauvais compromis.

Pourquoi il n’y a pas de framework

Le routage ici est un seul if sur la méthode et le chemin. Un framework n’apporte rien pour ça, et axum en particulier ne donne aucun moyen de définir un délai de lecture des en-têtes — précisément le garde-fou anti-slowloris qui a motivé le passage à l’asynchrone. Donc : hyper directement.

Dépendances

64 crates, 2,4 Mo statique sur ARMv7 (1,3 Mo sans SQLite). Directes :

CratePour
tokiole runtime, les timers, les signaux
hyper + hyper-util + http-body-utilHTTP/1, avec un délai de lecture des en-têtes
toml_editTOML, en préservant la mise en forme
serde_jsondocuments JSON, et aplatissement d’un corps de requête
serde_yaml_ngdocuments YAML
quick-xmldocuments XML
rusqlite (optionnelle, bundled)le store SQLite

Aucun serde derive nulle part. La règle d’origine était « ne jamais parser le corps de requête comme du JSON » ; elle a été assouplie délibérément, et l’énoncé honnête de l’état actuel est : le corps est parsé en une serde_json::Value non typée quand il se trouve être du JSON, uniquement pour en récolter des faits. Rien n’est désérialisé dans une struct, donc aucune hypothèse sur le schéma de Proxmox n’est gravée dans un type. Un corps qui n’est pas du JSON n’est pas une erreur — il apporte la botte de foin et rien de plus. Voir sélection.

Ajouter une dépendance exige une raison dans le message de commit. Ce binaire tourne en root sur le matériel d’autres gens.

Mis à jour le Modifier cette page