feat(app-tauri): cycle de vie du serveur embarqué et persistance de l'exposition (#68 B2)
Permet d'activer le serveur web depuis l'app desktop — la fonctionnalité demandée à l'origine du ticket. - `embedded_server.rs` : `EmbeddedServerController` porté par `AppState`, avec les commandes `embedded_server_start` / `stop` / `status` et `get` / `save` / `preview_server_exposure_settings`. - `FsServerExposureSettingsStore` : écrit `<app-data-dir>/deployment/server-exposure.json`. C'est de la config de sécurité, pas une préférence d'UI — d'où l'app-data-dir global plutôt que `localStorage`. Écriture atomique : temporaire, `0600` posé AVANT le `rename`, donc le fichier final n'est jamais brièvement lisible par tous. - Arrêt du serveur câblé sur la sortie de l'app (`lib.rs`), aux côtés des autres nettoyages. Le serveur embarqué appelle `run_embedded_with_core` avec le `BackendCore` partagé du desktop, jamais `run_embedded` — c'est tout l'intérêt du seam ouvert par B1 : une seule composition root dans le processus, pas deux. DETTE CONNUE, consignée au carnet, non bloquante : - `preview_settings` valide avant de prévisualiser (`embedded_server.rs:211`), ce qui verrouille la liste des candidats : un brouillon en mode `remoteProxyOtherMachine` ne peut pas être prévisualisé tant qu'il n'a pas le `lanBindAddress` que cette liste doit justement fournir. Contourné côté frontend par une sonde `localOnly` toujours valide. Défaut d'ordonnancement, pas une fatalité. - Le warning `missingTrustedProxy` (`embedded_server.rs:452`) est du code mort : `validate_settings` rejette les `trustedProxies` vides en mode 3 avant que `preview_settings` ne puisse le produire. QA ré-exécutée par Git HORS SANDBOX (DevBackend et QA sont bloqués par EPERM sur `bind` ; un vert sandboxé ne prouve rien sur ces crates) : `cargo test -p app-tauri` 43 passed, 1 ignored · `-p web-server` 66 passed, 0 échec. Le test ignoré `mcp_bridge::tests::end_to_end_over_real_loopback` est PRÉEXISTANT sur `develop` et non touché par ce lot ; lancé explicitement hors sandbox avec `--ignored`, il passe. C'est une garde d'environnement, pas un faux vert — distinction qui nous a déjà coûté deux fois aujourd'hui. Invariants vérifiés par Git : appelant `run_embedded_with_core` confirmé, aucune fuite d'`EmbeddedServer*`/`ServerExposure*` dans `domain` ni `application`, ordre chmod/rename correct, `stop()` bien appelé à la fermeture. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -61,6 +61,9 @@ use crate::dto::{
|
||||
UpdateProjectContextRequestDto, UpdateProjectPermissionsRequestDto, UpdateSkillRequestDto,
|
||||
UpdateTemplateRequestDto, WriteTerminalRequestDto,
|
||||
};
|
||||
use crate::embedded_server::{
|
||||
EmbeddedServerStatusDto, ServerExposurePreviewDto, ServerExposureSettingsDto,
|
||||
};
|
||||
use crate::pty::{PtyBridge, PtyChunk};
|
||||
use crate::state::{AppState, FocusedProjectDto};
|
||||
use domain::{SkillRef, SkillScope};
|
||||
@ -83,6 +86,69 @@ pub fn health(
|
||||
.map_err(ErrorDto::from)
|
||||
}
|
||||
|
||||
/// `get_server_exposure_settings` — read persisted embedded-server exposure settings.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] when persisted settings are invalid.
|
||||
#[tauri::command]
|
||||
pub fn get_server_exposure_settings(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<ServerExposureSettingsDto, ErrorDto> {
|
||||
state.embedded_server.get_settings()
|
||||
}
|
||||
|
||||
/// `save_server_exposure_settings` — validate and persist embedded-server exposure settings.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] when settings are invalid or cannot be written.
|
||||
#[tauri::command]
|
||||
pub fn save_server_exposure_settings(
|
||||
settings: ServerExposureSettingsDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<(), ErrorDto> {
|
||||
state.embedded_server.save_settings(settings)
|
||||
}
|
||||
|
||||
/// `preview_server_exposure_settings` — derive LAN candidates and upstream URL.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] when settings are invalid.
|
||||
#[tauri::command]
|
||||
pub fn preview_server_exposure_settings(
|
||||
settings: ServerExposureSettingsDto,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<ServerExposurePreviewDto, ErrorDto> {
|
||||
state.embedded_server.preview(settings)
|
||||
}
|
||||
|
||||
/// `embedded_server_status` — return the current embedded server status.
|
||||
#[tauri::command]
|
||||
pub fn embedded_server_status(state: State<'_, AppState>) -> EmbeddedServerStatusDto {
|
||||
state.embedded_server.status()
|
||||
}
|
||||
|
||||
/// `embedded_server_start` — start the embedded server with the shared backend core.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] when settings are invalid or the listener cannot start.
|
||||
#[tauri::command]
|
||||
pub async fn embedded_server_start(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<EmbeddedServerStatusDto, ErrorDto> {
|
||||
state.embedded_server.start(state.core()).await
|
||||
}
|
||||
|
||||
/// `embedded_server_stop` — stop the embedded server.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an [`ErrorDto`] when stopping the listener fails.
|
||||
#[tauri::command]
|
||||
pub async fn embedded_server_stop(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<EmbeddedServerStatusDto, ErrorDto> {
|
||||
state.embedded_server.stop().await
|
||||
}
|
||||
|
||||
/// `create_project` — create a project from a root: init `.ideai/`, register it.
|
||||
///
|
||||
/// # Errors
|
||||
|
||||
Reference in New Issue
Block a user