update rustwarden-CLI

EBO <eric.bouhana@softalys.com> committé le 2026-05-12 08:48

2ed307e73b04b144e4ec92422ff575fcda57ec84

1 parent(s)

4 fichiers modifiés +301 -18
M README.md
+80 -2
@@ -18,6 +18,7 @@ Permet de creer rapidement des applications web avec authentification JWT, RBAC
18 18 - Middleware d'authentification et d'autorisation
19 19 - Conformite ANSSI (complexite des mots de passe, HttpOnly cookies, etc.)
20 20 - OAuth2 / OpenID Connect (Google, GitHub, Discord, Microsoft) avec architecture extensible
21 +- Federation ActivityPub optionnelle (RFC 0001+) : WebFinger, NodeInfo, login AP-native, invitations d'equipe cross-instance, modele de confiance allowlist/blocklist — fournie par le crate `rustwarden-fediverse`
21 22 - Variable `APP_NAME` pour personnaliser le branding partout dans l'UI
22 23
23 24 ## Architecture
@@ -25,8 +26,9 @@ Permet de creer rapidement des applications web avec authentification JWT, RBAC
25 26 ```
26 27 rustwarden/
27 28 ├── crates/
28 -│ ├── rustwarden-core/ # Library : auth, RBAC, services, middleware, i18n, hooks
29 -│ └── rustwarden-ui/ # UI optionnelle : templates HTML, admin panel, handlers
29 +│ ├── rustwarden-core/ # Library : auth, RBAC, services, middleware, i18n, hooks
30 +│ ├── rustwarden-ui/ # UI optionnelle : templates HTML, admin panel, handlers
31 +│ └── rustwarden-fediverse/ # Federation ActivityPub optionnelle (RFC 0001+)
30 32 ├── app/ # Application de reference (12 lignes de main.rs)
31 33 ├── tools/
32 34 │ └── rustwarden-cli/ # Wizard interactif pour creer de nouveaux projets
@@ -42,6 +44,7 @@ rustwarden/
42 44 |-------|-------------|
43 45 | `rustwarden-core` | Authentification JWT, modeles SeaORM, services (users, roles, permissions, email), middleware (auth, admin, CSRF, rate-limit), i18n, background tasks, hooks |
44 46 | `rustwarden-ui` | Interface HTML par defaut (login, profil, admin panel) avec Askama, DaisyUI, HTMX |
47 +| `rustwarden-fediverse` | Federation ActivityPub (WebFinger, NodeInfo, actor, inbox, signatures HTTP, auth AP-native, invitations cross-instance, worker outbound, modele de confiance allowlist/blocklist) — optionnel via feature Cargo `fediverse` |
45 48 | `rustwarden-app` | Binaire de reference utilisant le builder |
46 49 | `rustwarden-cli` | Wizard CLI interactif pour scaffolder des applications autonomes |
47 50
@@ -274,6 +277,37 @@ RustwardenApp::builder()
274 277 | PUT | `/admin/settings` | Mettre a jour les parametres |
275 278 | GET | `/admin/errors` | Page de test d'erreurs |
276 279
280 +### Pages admin federation *(feature `fediverse`)*
281 +
282 +Quand le binaire est compile avec `--features fediverse`, le dashboard admin affiche une carte "Federation ActivityPub" qui pointe vers `/admin/fediverse/settings`. Depuis ce hub :
283 +
284 +| Methode | Chemin | Description |
285 +|---------|--------|-------------|
286 +| GET | `/admin/fediverse/settings` | Toggle d'activation + selecteurs (mode de confiance, scope d'interop, authorized-fetch) |
287 +| POST | `/admin/fediverse/settings` | Sauvegarder les reglages |
288 +| GET | `/admin/fediverse/trust_list` | Liste de confiance (allowlist ou blocklist selon le mode) |
289 +| POST | `/admin/fediverse/trust_list/add` | Ajouter un domaine |
290 +| POST | `/admin/fediverse/trust_list/delete` | Retirer un domaine |
291 +| GET | `/admin/fediverse/queue` | Dashboard de la file d'envoi outbound (RFC 0011) |
292 +| GET | `/admin/fediverse/sessions_remote` | Sessions distantes actives |
293 +| POST | `/admin/fediverse/sessions_remote/revoke` | Revoquer une session distante |
294 +| POST | `/admin/fediverse/sessions_remote/kill_domain` | Kill-switch par domaine |
295 +| GET | `/admin/fediverse/nodeinfo` | Stats NodeInfo humanisees |
296 +| GET | `/admin/teams/{id}/invites` | Invitations d'equipe cross-instance |
297 +
298 +Cote utilisateur final, le flow de connexion federee est expose sur `/login/fediverse` (et son SSE `/login/fediverse/sse/{id}`), l'ecran de consentement IdP sur `/fediverse/consent/{id}`, et la page d'acceptation d'invitation sur `/fediverse/invite/{id}`. Le profil utilisateur expose `/profile/connected_apps` pour lister les instances qu'il a autorisees.
299 +
300 +### Endpoints protocole (publics, hors UI) *(feature `fediverse`)*
301 +
302 +| Methode | Chemin | Description |
303 +|---------|--------|-------------|
304 +| GET | `/.well-known/webfinger?resource=acct:user@domain` | Resolution WebFinger RFC 7033 |
305 +| GET | `/.well-known/nodeinfo` | Discovery NodeInfo |
306 +| GET | `/nodeinfo/2.1` | Document NodeInfo 2.1 |
307 +| GET | `/users/{username}` | Person actor JSON (avec content negotiation) |
308 +| POST | `/inbox` | Inbox AP (Update / Delete / Revoke) |
309 +| POST | `/fediverse/auth/inbox` | Inbox AP-native pour `AuthRequest` / `Accept` / `Reject` |
310 +
277 311 ---
278 312
279 313 ## Hooks (extension)
@@ -490,6 +524,47 @@ Le panneau d'administration (`/admin/roles`) permet de :
490 524
491 525 > Les credentials OAuth peuvent etre configures via l'interface Admin (`/admin/settings`) ou via ces variables d'environnement (fallback).
492 526
527 +### Federation ActivityPub *(feature `fediverse`)*
528 +
529 +| Variable | Description | Defaut |
530 +|----------|-------------|--------|
531 +| `FEDIVERSE_DOMAIN` | Domaine federe expose, ex. `example.org`. Refuse les placeholders type `example.com`, `your.tld`, `change-me.tld`. | requis |
532 +| `FEDIVERSE_DEBUG_LOCAL_FEDERATION` | Autorise les base URLs `http://` (tests locaux multi-instances). **Ne jamais activer en production.** | `false` |
533 +| `FEDIVERSE_AUTHORIZED_FETCH` | Mode "secure fetch" Mastodon : exige une signature HTTP pour resoudre un acteur. Activer apres audit. | `false` |
534 +| `FEDIVERSE_OUTBOUND_WORKERS` | Nombre de workers concurrents qui drainent la file de delivery. | `4` |
535 +| `FEDIVERSE_OUTBOUND_MAX_ATTEMPTS` | Tentatives maximum avant qu'un job soit marque `dead`. | `8` |
536 +| `FEDIVERSE_OUTBOUND_REQUEST_TIMEOUT_SECS` | Timeout par requete outbound. | `30` |
537 +| `FEDIVERSE_FETCH_ACTOR_CACHE_TTL_SECS` | TTL du cache des documents acteur distants. | `86400` |
538 +| `FEDIVERSE_AUTH_CHALLENGE_TTL_SECS` | TTL des challenges d'auth AP-native (RFC 0006). | `300` |
539 +
540 +> La feature est **opt-in a la compilation** : ajouter `--features fediverse` au binaire. Le crate `rustwarden-core` ne tire **aucune** des deps lourdes (`activitypub_federation`, `rsa`, `reqwest`, `ammonia`) tant que le binaire n'active pas la feature. Cote runtime, deux flags supplementaires dans `app_settings` controlent l'experience admin : `fediverse_enabled` (toggle global, defaut `false`) et `fediverse_trust_mode` (`allowlist` / `open_with_blocklist`). Reglage via `/admin/fediverse/settings` ou directement en SQL.
541 +
542 +Wiring minimal dans `main.rs` (extrait de [`app/src/main.rs`](app/src/main.rs)) :
543 +
544 +```rust
545 +#[cfg(feature = "fediverse")]
546 +let builder = {
547 + use std::sync::Arc;
548 + let db = sea_orm::Database::connect(&std::env::var("DATABASE_URL")?).await?;
549 + let config = rustwarden_fediverse::FediverseConfig::from_env()?;
550 + let router = rustwarden_fediverse::routes::router(config, db.clone());
551 + let checker: Arc<dyn rustwarden_core::middleware::revocation::RemoteIdentityRevocationChecker> =
552 + Arc::new(
553 + rustwarden_fediverse::services::revocation::SeaOrmRemoteIdentityRevocationChecker::new(
554 + db.clone(),
555 + ),
556 + );
557 + builder
558 + .database(db)
559 + .register_protected_prefix("/fediverse/consent")
560 + .register_protected_prefix("/fediverse/invite")
561 + .register_revocation_checker(checker)
562 + .merge_routes(router)
563 +};
564 +```
565 +
566 +Pour un essai local multi-instances (3 noeuds federes via Docker Compose), voir [`tests/fediverse-testbed/README.md`](tests/fediverse-testbed/README.md). Guide operateur complet : [`docs/fediverse-operations.md`](docs/fediverse-operations.md).
567 +
493 568 ---
494 569
495 570 ## Migrations
@@ -581,4 +656,7 @@ La locale est detectee automatiquement dans cet ordre :
581 656 | [docs/ANSSI_COMPLIANCE_REPORT.md](docs/ANSSI_COMPLIANCE_REPORT.md) | Rapport de conformite aux recommandations ANSSI |
582 657 | [docs/CSS_STYLE_GUIDE.md](docs/CSS_STYLE_GUIDE.md) | Guide de style CSS/DaisyUI |
583 658 | [docs/configuration-flows.md](docs/configuration-flows.md) | Diagrammes des flux de configuration |
659 +| [docs/fediverse-operations.md](docs/fediverse-operations.md) | Guide operateur de la federation ActivityPub (10 sections : activation, env vars, app_settings, trust/block list, incidents, observabilite, GC, promotion stable, URLs, limites) |
660 +| [docs/rfcs/](docs/rfcs/) | 25 RFCs documentant la conception de la couche federation (0001–0025) |
661 +| [tests/fediverse-testbed/README.md](tests/fediverse-testbed/README.md) | Banc d'essai Docker tri-instance (alpha/beta/gamma) pour valider la federation Rustwarden↔Rustwarden |
584 662 | `.env.sample` | Fichier de reference pour toutes les variables d'environnement |
M tools/rustwarden-cli/src/start_project.rs
+62 -0
@@ -29,6 +29,7 @@ pub fn run() -> Result<(), Box<dyn std::error::Error>> {
29 29 handle_existing_dir(&project_dir)?;
30 30
31 31 let with_ui = ask_ui_mode()?;
32 + let (with_fediverse, fediverse_domain) = ask_fediverse_mode()?;
32 33 let auto_migrate = ask_auto_migrate()?;
33 34 let port = ask_port()?;
34 35
@@ -51,6 +52,8 @@ pub fn run() -> Result<(), Box<dyn std::error::Error>> {
51 52 app_display_name: app_display_name.clone(),
52 53 description: description.clone(),
53 54 with_ui,
55 + with_fediverse,
56 + fediverse_domain,
54 57 auto_migrate,
55 58 port,
56 59 db_name,
@@ -73,6 +76,9 @@ pub fn run() -> Result<(), Box<dyn std::error::Error>> {
73 76 if with_ui {
74 77 copy_crate(&rustwarden_root, &project_dir, "rustwarden-ui")?;
75 78 }
79 + if with_fediverse {
80 + copy_crate(&rustwarden_root, &project_dir, "rustwarden-fediverse")?;
81 + }
76 82
77 83 copy_resources(&rustwarden_root, &project_dir, with_ui)?;
78 84 copy_docs(&rustwarden_root, &project_dir)?;
@@ -201,6 +207,62 @@ fn handle_existing_dir(project_dir: &Path) -> Result<(), Box<dyn std::error::Err
201 207 Ok(())
202 208 }
203 209
210 +/// Demande si le projet doit etre genere avec la couche federation
211 +/// ActivityPub (crate `rustwarden-fediverse`). Si oui, demande aussi
212 +/// le domaine federe a placer dans `FEDIVERSE_DOMAIN`.
213 +///
214 +/// Retourne `(with_fediverse, Some(domain))` ou `(false, None)`.
215 +fn ask_fediverse_mode() -> Result<(bool, Option<String>), Box<dyn std::error::Error>> {
216 + let enable = Confirm::new()
217 + .with_prompt(format!(
218 + "{}",
219 + style("Activer la federation ActivityPub (RFC 0001+, WebFinger, AP-native auth, invitations cross-instance) ?").cyan()
220 + ))
221 + .default(false)
222 + .interact()?;
223 +
224 + if !enable {
225 + return Ok((false, None));
226 + }
227 +
228 + let domain: String = Input::new()
229 + .with_prompt(format!(
230 + "{}",
231 + style("Domaine federe (FEDIVERSE_DOMAIN, ex. example.org)").cyan()
232 + ))
233 + .default("example.org".into())
234 + .validate_with(|input: &String| -> Result<(), String> {
235 + let trimmed = input.trim();
236 + if trimmed.is_empty() {
237 + return Err("Le domaine ne peut pas etre vide".into());
238 + }
239 + // Refuse les placeholders attendus par FediverseConfig::from_env.
240 + let lower = trimmed.to_ascii_lowercase();
241 + let stripped = lower
242 + .trim_start_matches("http://")
243 + .trim_start_matches("https://")
244 + .trim_end_matches('/');
245 + let placeholders = [
246 + "example.com",
247 + "example.net",
248 + "localhost.localdomain",
249 + "your.tld",
250 + "your-domain.tld",
251 + "domain.tld",
252 + "change-me.tld",
253 + ];
254 + if placeholders.contains(&stripped) {
255 + return Err(format!(
256 + "`{stripped}` est un placeholder refuse par FediverseConfig — saisir un vrai domaine"
257 + ));
258 + }
259 + Ok(())
260 + })
261 + .interact_text()?;
262 +
263 + Ok((true, Some(domain.trim().to_string())))
264 +}
265 +
204 266 fn ask_ui_mode() -> Result<bool, Box<dyn std::error::Error>> {
205 267 let options = vec!["Avec interface web (rustwarden-ui)", "API seule (headless)"];
206 268 let selection = Select::new()
M tools/rustwarden-cli/src/templates.rs
+124 -12
@@ -34,6 +34,14 @@ pub struct ProjectConfig {
34 34 pub app_display_name: String,
35 35 pub description: String,
36 36 pub with_ui: bool,
37 + /// Active la federation ActivityPub via le crate `rustwarden-fediverse`.
38 + /// Si `true` : copie le crate, ajoute la feature `fediverse` au Cargo.toml,
39 + /// injecte le wiring dans `main.rs` et seede les variables `FEDIVERSE_*`.
40 + pub with_fediverse: bool,
41 + /// Domaine federe declare au boot quand `with_fediverse = true`
42 + /// (`FEDIVERSE_DOMAIN`). Rester `None` quand la federation est
43 + /// desactivee.
44 + pub fediverse_domain: Option<String>,
37 45 pub auto_migrate: bool,
38 46 pub port: u16,
39 47 pub db_name: String,
@@ -54,15 +62,40 @@ pub fn cargo_toml(cfg: &ProjectConfig) -> String {
54 62 ""
55 63 };
56 64
65 + let fediverse_member = if cfg.with_fediverse {
66 + ", \"crates/rustwarden-fediverse\""
67 + } else {
68 + ""
69 + };
70 +
57 71 let ui_dep = if cfg.with_ui {
58 72 "\nrustwarden-ui = { path = \"crates/rustwarden-ui\" }"
59 73 } else {
60 74 ""
61 75 };
62 76
77 + let fediverse_dep = if cfg.with_fediverse {
78 + "\nrustwarden-fediverse = { path = \"crates/rustwarden-fediverse\", optional = true }"
79 + } else {
80 + ""
81 + };
82 +
83 + let features_section = if cfg.with_fediverse {
84 + let ui_feature = if cfg.with_ui {
85 + ", \"rustwarden-ui/fediverse\""
86 + } else {
87 + ""
88 + };
89 + format!(
90 + "\n[features]\ndefault = [\"fediverse\"]\n# Federation ActivityPub (RFC 0001+).\n# Activable a la compilation. Cf. README §Federation ActivityPub.\nfediverse = [\"dep:rustwarden-fediverse\"{ui_feature}]\n"
91 + )
92 + } else {
93 + String::new()
94 + };
95 +
63 96 format!(
64 97 r#"[workspace]
65 -members = ["crates/rustwarden-core"{ui_member}]
98 +members = ["crates/rustwarden-core"{ui_member}{fediverse_member}]
66 99 resolver = "2"
67 100
68 101 [workspace.lints.clippy]
@@ -77,9 +110,9 @@ description = "{description}"
77 110 [[bin]]
78 111 name = "{name}"
79 112 path = "src/main.rs"
80 -
113 +{features_section}
81 114 [dependencies]
82 -rustwarden-core = {{ path = "crates/rustwarden-core" }}{ui_dep}
115 +rustwarden-core = {{ path = "crates/rustwarden-core" }}{ui_dep}{fediverse_dep}
83 116 axum = {{ version = "0.8", features = ["macros"] }}
84 117 tokio = {{ version = "1", features = ["full"] }}
85 118 sea-orm = {{ version = "2.0.0-rc.20", features = ["sqlx-postgres", "runtime-tokio-native-tls", "macros"] }}
@@ -94,7 +127,10 @@ empty_line_after_doc_comments = "allow"
94 127 name = cfg.name,
95 128 description = cfg.description,
96 129 ui_member = ui_member,
130 + fediverse_member = fediverse_member,
97 131 ui_dep = ui_dep,
132 + fediverse_dep = fediverse_dep,
133 + features_section = features_section,
98 134 )
99 135 }
100 136
@@ -123,21 +159,65 @@ pub fn main_rs(cfg: &ProjectConfig) -> String {
123 159 ""
124 160 };
125 161
162 + // Wiring conditionnel de la federation ActivityPub. Casse la chaine
163 + // du builder en 3 etapes pour permettre l'ouverture precoce de la
164 + // connexion DB requise par le router fediverse.
165 + let (builder_open, fediverse_block, builder_close) = if cfg.with_fediverse {
166 + (
167 + "let builder = RustwardenApp::builder()\n",
168 + r#"
169 + #[cfg(feature = "fediverse")]
170 + let builder = {
171 + use std::sync::Arc;
172 + let db_url = std::env::var("DATABASE_URL")
173 + .map_err(|_| anyhow::anyhow!("DATABASE_URL doit etre defini pour la federation"))?;
174 + let db = sea_orm::Database::connect(&db_url).await?;
175 + let fediverse_config = rustwarden_fediverse::FediverseConfig::from_env()
176 + .map_err(|e| anyhow::anyhow!("FediverseConfig invalide : {e}"))?;
177 + let fediverse_router =
178 + rustwarden_fediverse::routes::router(fediverse_config, db.clone());
179 + let checker: Arc<
180 + dyn rustwarden_core::middleware::revocation::RemoteIdentityRevocationChecker,
181 + > = Arc::new(
182 + rustwarden_fediverse::services::revocation::SeaOrmRemoteIdentityRevocationChecker::new(
183 + db.clone(),
184 + ),
185 + );
186 + builder
187 + .database(db)
188 + .register_protected_prefix("/fediverse/consent")
189 + .register_protected_prefix("/fediverse/invite")
190 + .register_revocation_checker(checker)
191 + .merge_routes(fediverse_router)
192 + };
193 +
194 + builder
195 +"#,
196 + " .merge_routes(app_routes())\n .build()\n .await?\n .run()\n .await\n",
197 + )
198 + } else {
199 + (
200 + " RustwardenApp::builder()\n",
201 + "",
202 + " .merge_routes(app_routes())\n .build()\n .await?\n .run()\n .await\n",
203 + )
204 + };
205 +
206 + let fediverse_use = if cfg.with_fediverse {
207 + "#[allow(unused_imports)]\nuse rustwarden_core::middleware::revocation::RemoteIdentityRevocationChecker;\n"
208 + } else {
209 + ""
210 + };
211 +
126 212 format!(
127 213 r#"use axum::{{routing::get, Router}};
128 214 use rustwarden_core::prelude::*;
129 215 use sea_orm::DatabaseConnection;
130 -
216 +{fediverse_use}
131 217 #[tokio::main]
132 218 async fn main() -> anyhow::Result<()> {{
133 - RustwardenApp::builder()
134 - .from_env()
135 -{ui_line}{migrate_line}{email_line}{address_line} .merge_routes(app_routes())
136 - .build()
137 - .await?
138 - .run()
139 - .await
140 -}}
219 + {builder_open} .from_env()
220 +{ui_line}{migrate_line}{email_line}{address_line}{fediverse_block}{builder_close}}}
141 221
142 222 fn app_routes() -> Router<DatabaseConnection> {{
143 223 Router::new()
@@ -230,6 +310,36 @@ OAUTH_ENCRYPTION_KEY={encryption_key}
230 310 }
231 311 }
232 312
313 +fn fediverse_env_section(cfg: &ProjectConfig) -> String {
314 + if cfg.with_fediverse {
315 + let domain = cfg
316 + .fediverse_domain
317 + .clone()
318 + .unwrap_or_else(|| "localhost".to_string());
319 + format!(
320 + r#"# --- Federation ActivityPub (RFC 0001+) ---
321 +# Active uniquement si le binaire est compile avec --features fediverse.
322 +FEDIVERSE_DOMAIN={domain}
323 +# FEDIVERSE_DEBUG_LOCAL_FEDERATION=true # autorise http:// (dev local uniquement)
324 +# FEDIVERSE_AUTHORIZED_FETCH=false # exiger signature sur fetch acteur
325 +# FEDIVERSE_OUTBOUND_WORKERS=4
326 +# FEDIVERSE_OUTBOUND_MAX_ATTEMPTS=8
327 +# FEDIVERSE_OUTBOUND_REQUEST_TIMEOUT_SECS=30
328 +# FEDIVERSE_FETCH_ACTOR_CACHE_TTL_SECS=86400
329 +# FEDIVERSE_AUTH_CHALLENGE_TTL_SECS=300
330 +"#
331 + )
332 + } else {
333 + r#"# --- Federation ActivityPub (RFC 0001+, desactivee) ---
334 +# Pour activer : recompiler avec `--features fediverse` puis decommenter.
335 +# FEDIVERSE_DOMAIN=example.org
336 +# FEDIVERSE_DEBUG_LOCAL_FEDERATION=false
337 +# FEDIVERSE_AUTHORIZED_FETCH=false
338 +"#
339 + .to_string()
340 + }
341 +}
342 +
233 343 pub fn dot_env(cfg: &ProjectConfig) -> String {
234 344 let smtp_section = match &cfg.smtp {
235 345 Some(smtp) => format!(
@@ -345,6 +455,7 @@ DEFAULT_LOCALE=fr
345 455 # IMAP_POLL_INTERVAL_SECONDS=300
346 456
347 457 {oauth_section}
458 +{fediverse_section}
348 459 "#,
349 460 name = cfg.name,
350 461 app_display_name = cfg.app_display_name,
@@ -358,6 +469,7 @@ DEFAULT_LOCALE=fr
358 469 admin_password = cfg.admin_password,
359 470 smtp_section = smtp_section,
360 471 oauth_section = oauth_env_section(cfg),
472 + fediverse_section = fediverse_env_section(cfg),
361 473 )
362 474 }
363 475
M tools/rustwarden-cli/src/update_project.rs
+35 -4
@@ -7,7 +7,11 @@ use dialoguer::{Confirm, Input};
7 7 use crate::version;
8 8
9 9 /// Fichiers et dossiers du framework qui sont remplaces lors de la mise a jour.
10 -const FRAMEWORK_CRATES: &[&str] = &["rustwarden-core", "rustwarden-ui"];
10 +/// `rustwarden-core` est toujours synchronise. `rustwarden-ui` et
11 +/// `rustwarden-fediverse` ne sont synchronises que s'ils sont presents
12 +/// dans le projet consommateur (detection en debut de `run()`).
13 +const FRAMEWORK_CRATES: &[&str] =
14 + &["rustwarden-core", "rustwarden-ui", "rustwarden-fediverse"];
11 15
12 16 /// Fichiers du projet consommateur qu'on ne touche JAMAIS.
13 17 const PROTECTED_FILES: &[&str] = &[
@@ -41,9 +45,10 @@ pub fn run() -> Result<(), Box<dyn std::error::Error>> {
41 45 print_version_info(&current_version, &new_version);
42 46
43 47 let has_ui = project_dir.join("crates/rustwarden-ui").exists();
48 + let has_fediverse = project_dir.join("crates/rustwarden-fediverse").exists();
44 49 let custom_templates = detect_custom_templates(&project_dir, &rustwarden_root, has_ui);
45 50
46 - print_update_plan(&project_dir, has_ui, &custom_templates);
51 + print_update_plan(&project_dir, has_ui, has_fediverse, &custom_templates);
47 52
48 53 if !Confirm::new()
49 54 .with_prompt(format!("{}", style("Proceder a la mise a jour ?").cyan()))
@@ -58,7 +63,13 @@ pub fn run() -> Result<(), Box<dyn std::error::Error>> {
58 63 let backup_dir = create_backup(&project_dir)?;
59 64
60 65 // --- Mise a jour des crates ---
61 - let stats = update_crates(&rustwarden_root, &project_dir, has_ui, &custom_templates)?;
66 + let stats = update_crates(
67 + &rustwarden_root,
68 + &project_dir,
69 + has_ui,
70 + has_fediverse,
71 + &custom_templates,
72 + )?;
62 73
63 74 // --- Reecrire le fichier de version ---
64 75 version::write_version(&project_dir, &new_version)?;
@@ -207,7 +218,12 @@ fn print_version_info(current: &Option<String>, new: &str) {
207 218 }
208 219
209 220 /// Affiche le plan de mise a jour.
210 -fn print_update_plan(project_dir: &Path, has_ui: bool, custom_templates: &[String]) {
221 +fn print_update_plan(
222 + project_dir: &Path,
223 + has_ui: bool,
224 + has_fediverse: bool,
225 + custom_templates: &[String],
226 +) {
211 227 println!("\n{}", style("── Plan de mise a jour ──").cyan().bold());
212 228
213 229 println!(" {} {}", style("Projet :").dim(), project_dir.display());
@@ -217,6 +233,9 @@ fn print_update_plan(project_dir: &Path, has_ui: bool, custom_templates: &[Strin
217 233 if has_ui {
218 234 println!(" - crates/rustwarden-ui/");
219 235 }
236 + if has_fediverse {
237 + println!(" - crates/rustwarden-fediverse/");
238 + }
220 239
221 240 println!(
222 241 "\n {} :",
@@ -389,6 +408,7 @@ fn update_crates(
389 408 rustwarden_root: &Path,
390 409 project_dir: &Path,
391 410 has_ui: bool,
411 + has_fediverse: bool,
392 412 custom_templates: &[String],
393 413 ) -> Result<SyncStats, Box<dyn std::error::Error>> {
394 414 println!("\n{}", style("── Mise a jour des crates ──").cyan().bold());
@@ -415,6 +435,17 @@ fn update_crates(
415 435 )?;
416 436 }
417 437
438 + // rustwarden-fediverse : uniquement si le projet l'utilise
439 + if has_fediverse {
440 + sync_crate(
441 + rustwarden_root,
442 + project_dir,
443 + "rustwarden-fediverse",
444 + &[],
445 + &mut stats,
446 + )?;
447 + }
448 +
418 449 Ok(stats)
419 450 }
420 451