fix: --config flag ignoré par la commande serve + Config::load_from()

- Ajout de Config::load_from(path) pour charger depuis un chemin custom
- cmd_serve() accepte maintenant un Option<PathBuf> pour --config
- Le flag --config du CLI est maintenant passé à cmd_serve()
- Version bump 0.2.1 → 0.2.2

Corrige le bug où le service Windows NSSM passait --config mais
la commande serve lisait toujours depuis %APPDATA%/imagod-web/imagod.toml
This commit is contained in:
2026-06-30 09:33:22 -04:00
parent 8db614cd0b
commit 08dca81e7c
12 changed files with 272 additions and 72 deletions
Generated
+3 -1
View File
@@ -667,6 +667,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink",
] ]
[[package]] [[package]]
@@ -1285,7 +1286,7 @@ dependencies = [
[[package]] [[package]]
name = "imagod-web" name = "imagod-web"
version = "0.2.0" version = "0.2.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arboard", "arboard",
@@ -2237,6 +2238,7 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
dependencies = [ dependencies = [
"base64", "base64",
"bytes", "bytes",
"futures-channel",
"futures-core", "futures-core",
"futures-util", "futures-util",
"http", "http",
+2 -2
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "imagod-web" name = "imagod-web"
version = "0.2.0" version = "0.2.2"
edition = "2021" edition = "2021"
description = "Imago Daemon + Dashboard — Service de capture automatique de screenshots + Dashboard web embarqué" description = "Imago Daemon + Dashboard — Service de capture automatique de screenshots + Dashboard web embarqué"
authors = ["Bruno Charest <[email protected]>"] authors = ["Bruno Charest <[email protected]>"]
@@ -15,7 +15,7 @@ clap = { version = "4", features = ["derive", "env"] }
# Async runtime # Async runtime
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
# HTTP Client (API Imago) — rustls pour éviter OpenSSL # HTTP Client (API Imago) — rustls pour éviter OpenSSL
reqwest = { version = "0.12", default-features = false, features = ["json", "multipart", "stream", "rustls-tls"] } reqwest = { version = "0.12", default-features = false, features = ["json", "multipart", "stream", "rustls-tls", "blocking"] }
# Serialization # Serialization
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
Binary file not shown.
Binary file not shown.
+60 -35
View File
@@ -1,80 +1,105 @@
//! Parsing CLI avec clap //! Parsing CLI avec clap
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use std::path::PathBuf;
#[derive(Parser)] #[derive(Parser)]
#[command( #[command(
name = "imagod-web", name = "imagod-web",
version = env!("CARGO_PKG_VERSION"), version = env!("IMAGOD_BUILD_VERSION"),
about = "Imago Daemon + Dashboard — Capture automatique de screenshots + Dashboard web embarqué", about = "Imago Daemon + Dashboard — Watcher clipboard et dashboard web",
long_about = "Service de capture automatique de screenshots vers Imago avec dashboard web embarqué.\n\n\ long_about = "Companion multiplateforme pour le backend Imago. Dashboard web, watcher clipboard, gestionnaire d'upload."
Modes :\n \
Windows : imagod-web start → watcher clipboard + dashboard\n \
Linux : imagod-web serve → watcher dossier + dashboard"
)] )]
pub struct Args { pub struct Cli {
#[command(subcommand)] #[command(subcommand)]
pub command: Command, pub cmd: Command,
} }
#[derive(Subcommand)] #[derive(Subcommand)]
pub enum Command { pub enum Command {
/// Lancer le watcher + dashboard (Windows uniquement) /// Lancer le watcher + dashboard
Start { Start {
/// Port du dashboard web /// Port d'écoute
#[arg(long, default_value = "9090")] #[arg(long, default_value = "9199")]
port: u16, port: u16,
/// Ouvrir le dashboard dans le navigateur
/// Ouvrir le navigateur
#[arg(long)] #[arg(long)]
open_browser: bool, open_browser: bool,
}, },
/// Lancer le dashboard web uniquement (Linux/serveur)
/// Lancer le dashboard web (+ watcher dossier sur Linux)
Serve { Serve {
/// Port du dashboard web /// Port d'écoute
#[arg(long, default_value = "9090")] #[arg(long, default_value = "9199")]
port: u16, port: u16,
/// Interface d'écoute
/// Adresse d'écoute
#[arg(long, default_value = "127.0.0.1")] #[arg(long, default_value = "127.0.0.1")]
host: String, host: String,
/// Désactiver le watcher clipboard
/// Désactiver le watcher (surveillance du presse-papier)
#[arg(long)] #[arg(long)]
no_watcher: bool, no_watcher: bool,
/// Ouvrir le dashboard dans le navigateur
/// Ouvrir le navigateur
#[arg(long)] #[arg(long)]
open_browser: bool, open_browser: bool,
/// Chemin du fichier de configuration
#[arg(long)]
config: Option<PathBuf>,
}, },
/// Arrêter le service /// Arrêter le service
Stop, Stop,
/// Afficher l'état du service
/// Afficher l'état
Status, Status,
/// Gérer la configuration /// Gérer la configuration
Config { Config {
#[command(subcommand)] #[command(subcommand)]
action: ConfigAction, action: ConfigAction,
}, },
/// Installer comme service Windows
Install, /// Installer en tant que service Windows
Install {
/// Chemin du fichier de configuration
#[arg(short, long)]
config: Option<PathBuf>,
/// Clé API Imago
#[arg(short, long)]
api_key: Option<String>,
},
/// Désinstaller le service Windows /// Désinstaller le service Windows
Uninstall, Uninstall,
/// Afficher les derniers logs
Logs, /// Mettre à jour depuis Nexus
Update {
/// Forcer même si déjà à jour
#[arg(short, long)]
force: bool,
/// URL du serveur Nexus
#[arg(short, long, default_value = "https://nexus.dracodev.net")]
nexus_url: String,
},
} }
#[derive(Subcommand, Debug)] #[derive(Subcommand)]
pub enum ConfigAction { pub enum ConfigAction {
/// Afficher la configuration actuelle /// Afficher la configuration
Show, Show,
/// Modifier un paramètre /// Modifier une clé
Set { Set { key: String, value: String },
/// Clé de configuration (ex: imago.url) /// Réinitialiser
key: String,
/// Valeur
value: String,
},
/// Réinitialiser la configuration par défaut
Reset, Reset,
} }
pub fn parse() -> Args { /// Parse les arguments CLI
Args::parse() pub fn parse() -> Command {
<Cli as clap::Parser>::parse().cmd
} }
+12 -2
View File
@@ -160,15 +160,25 @@ impl Config {
/// Charge la configuration depuis `%APPDATA%/imagod-web/imagod.toml` /// Charge la configuration depuis `%APPDATA%/imagod-web/imagod.toml`
pub fn load() -> Result<Self> { pub fn load() -> Result<Self> {
let path = Self::config_path()?; let path = Self::config_path()?;
Self::load_from(&path)
}
/// Charge la configuration depuis un chemin spécifique
pub fn load_from(path: &PathBuf) -> Result<Self> {
if path.exists() { if path.exists() {
let content = std::fs::read_to_string(&path) let content = std::fs::read_to_string(path)
.with_context(|| format!("Impossible de lire {}", path.display()))?; .with_context(|| format!("Impossible de lire {}", path.display()))?;
let config: Config = toml::from_str(&content) let config: Config = toml::from_str(&content)
.with_context(|| "Erreur de parsing du fichier de configuration")?; .with_context(|| "Erreur de parsing du fichier de configuration")?;
Ok(config) Ok(config)
} else { } else {
let config = Config::default(); let config = Config::default();
config.save()?; // Sauvegarder au chemin spécifié
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let content = toml::to_string_pretty(&config)?;
std::fs::write(path, &content)?;
Ok(config) Ok(config)
} }
} }
+3 -3
View File
@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Changelog — imagod-web v0.2.0+build.32-fb292ea-dirty</title> <title>Changelog — imagod-web v0.2.2+build.34-8db614c-dirty</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🖼️</text></svg>"> <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🖼️</text></svg>">
<style> <style>
* { margin:0; padding:0; box-sizing:border-box; } * { margin:0; padding:0; box-sizing:border-box; }
@@ -37,7 +37,7 @@ footer { text-align:center; color:#484f58; font-size:0.8rem; padding:2rem; }
</head> </head>
<body> <body>
<header> <header>
<h1>📋 imagod-web <span class="version-badge">v0.2.0+build.32-fb292ea-dirty</span></h1> <h1>📋 imagod-web <span class="version-badge">v0.2.2+build.34-8db614c-dirty</span></h1>
<nav> <nav>
<a href="/">🖼️ Galerie</a> <a href="/">🖼️ Galerie</a>
<a href="/changelog" class="active">📋 Changelog</a> <a href="/changelog" class="active">📋 Changelog</a>
@@ -107,6 +107,6 @@ footer { text-align:center; color:#484f58; font-size:0.8rem; padding:2rem; }
<h2>Légende des tags</h2> <h2>Légende des tags</h2>
</main> </main>
<footer>imagod-web v0.2.0+build.32-fb292ea-dirty — <a href="https://git.dracodev.net/Projets/imagod-web">Gitea</a></footer> <footer>imagod-web v0.2.2+build.34-8db614c-dirty — <a href="https://git.dracodev.net/Projets/imagod-web">Gitea</a></footer>
</body> </body>
</html> </html>
+3 -3
View File
@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Aide & Roadmap — imagod-web v0.2.0+build.32-fb292ea-dirty</title> <title>Aide & Roadmap — imagod-web v0.2.2+build.34-8db614c-dirty</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🖼️</text></svg>"> <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🖼️</text></svg>">
<style> <style>
* { margin:0; padding:0; box-sizing:border-box; } * { margin:0; padding:0; box-sizing:border-box; }
@@ -37,7 +37,7 @@ footer { text-align:center; color:#484f58; font-size:0.8rem; padding:2rem; }
</head> </head>
<body> <body>
<header> <header>
<h1>❓ imagod-web <span class="version-badge">v0.2.0+build.32-fb292ea-dirty</span></h1> <h1>❓ imagod-web <span class="version-badge">v0.2.2+build.34-8db614c-dirty</span></h1>
<nav> <nav>
<a href="/">🖼️ Galerie</a> <a href="/">🖼️ Galerie</a>
<a href="/changelog">📋 Changelog</a> <a href="/changelog">📋 Changelog</a>
@@ -169,6 +169,6 @@ footer { text-align:center; color:#484f58; font-size:0.8rem; padding:2rem; }
<p>5. <strong>Corriger le backend Imago</strong> (thumbnails, upload, stockage)</p> <p>5. <strong>Corriger le backend Imago</strong> (thumbnails, upload, stockage)</p>
</main> </main>
<footer>imagod-web v0.2.0+build.32-fb292ea-dirty — <a href="https://git.dracodev.net/Projets/imagod-web">Gitea</a></footer> <footer>imagod-web v0.2.2+build.34-8db614c-dirty — <a href="https://git.dracodev.net/Projets/imagod-web">Gitea</a></footer>
</body> </body>
</html> </html>
+1 -1
View File
@@ -465,7 +465,7 @@
<button class="btn" id="clear-btn" style="display:none">✕</button> <button class="btn" id="clear-btn" style="display:none">✕</button>
</div> </div>
<button class="btn accent" id="header-upload-btn" onclick="document.getElementById('drop-zone').scrollIntoView({behavior:'smooth'}); document.getElementById('file-input').click()">📤 Upload</button> <button class="btn accent" id="header-upload-btn" onclick="document.getElementById('drop-zone').scrollIntoView({behavior:'smooth'}); document.getElementById('file-input').click()">📤 Upload</button>
<span class="version-badge" id="version-badge">v0.2.0+build.32-fb292ea-dirty</span> <span class="version-badge" id="version-badge">v0.2.2+build.34-8db614c-dirty</span>
</div> </div>
<!-- ─── Navigation ─── --> <!-- ─── Navigation ─── -->
+32 -21
View File
@@ -21,13 +21,11 @@ mod watcher;
mod clipboard; mod clipboard;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
mod tray; mod tray;
#[cfg(target_os = "windows")]
mod service;
#[cfg(target_os = "windows")]
mod window_info;
mod server; mod server;
#[cfg(windows)]
mod service;
mod update;
mod window_info;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use std::path::PathBuf; use std::path::PathBuf;
use std::process; use std::process;
@@ -212,7 +210,7 @@ async fn cmd_start(port: u16, open_browser: bool) -> Result<()> {
} }
/// `serve` — Lance le dashboard web uniquement /// `serve` — Lance le dashboard web uniquement
async fn cmd_serve(host: &str, port: u16, no_watcher: bool, open_browser: bool) -> Result<()> { async fn cmd_serve(host: &str, port: u16, no_watcher: bool, open_browser: bool, config_path: Option<PathBuf>) -> Result<()> {
// Vérifier qu'il n'y a pas déjà une instance // Vérifier qu'il n'y a pas déjà une instance
if let Some(pid) = is_running() { if let Some(pid) = is_running() {
anyhow::bail!( anyhow::bail!(
@@ -222,7 +220,12 @@ async fn cmd_serve(host: &str, port: u16, no_watcher: bool, open_browser: bool)
); );
} }
let cfg = config::Config::load()?; let cfg = if let Some(ref path) = config_path {
tracing::info!("📄 Configuration chargée depuis : {}", path.display());
config::Config::load_from(path)?
} else {
config::Config::load()?
};
// Écrire le PID // Écrire le PID
write_pid()?; write_pid()?;
@@ -498,13 +501,12 @@ async fn main() {
setup_logging(); setup_logging();
let args = cli::parse(); let args = cli::parse();
let result = match args {
let result = match args.command {
cli::Command::Start { port, open_browser } => { cli::Command::Start { port, open_browser } => {
cmd_start(port, open_browser).await cmd_start(port, open_browser).await
} }
cli::Command::Serve { port, host, no_watcher, open_browser } => { cli::Command::Serve { port, host, no_watcher, open_browser, config } => {
cmd_serve(&host, port, no_watcher, open_browser).await cmd_serve(&host, port, no_watcher, open_browser, config).await
} }
cli::Command::Stop => { cli::Command::Stop => {
cmd_stop() cmd_stop()
@@ -515,17 +517,16 @@ async fn main() {
cli::Command::Config { action } => { cli::Command::Config { action } => {
cmd_config(&action) cmd_config(&action)
} }
cli::Command::Logs => { cli::Command::Install { config, api_key } => {
cmd_logs() install_service(config, api_key)
}
cli::Command::Install => {
install_service()
} }
cli::Command::Uninstall => { cli::Command::Uninstall => {
uninstall_service() uninstall_service()
} }
cli::Command::Update { force, nexus_url } => {
update_binary(force, &nexus_url)
}
}; };
if let Err(e) = result { if let Err(e) = result {
eprintln!("❌ Erreur : {:#}", e); eprintln!("❌ Erreur : {:#}", e);
process::exit(1); process::exit(1);
@@ -533,13 +534,13 @@ async fn main() {
} }
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
fn install_service() -> anyhow::Result<()> { fn install_service(config: Option<PathBuf>, api_key: Option<String>) -> anyhow::Result<()> {
tracing::info!("Installation du service Windows..."); tracing::info!("Installation du service Windows...");
service::install() service::install(config, api_key)
} }
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
fn install_service() -> anyhow::Result<()> { fn install_service(_config: Option<PathBuf>, _api_key: Option<String>) -> anyhow::Result<()> {
anyhow::bail!("La commande 'install' n'est disponible que sur Windows (service NSSM)") anyhow::bail!("La commande 'install' n'est disponible que sur Windows (service NSSM)")
} }
@@ -549,6 +550,16 @@ fn uninstall_service() -> anyhow::Result<()> {
service::uninstall() service::uninstall()
} }
#[cfg(windows)]
fn update_binary(_force: bool, _nexus_url: &str) -> anyhow::Result<()> {
crate::update::check_and_update(_force, _nexus_url)
}
#[cfg(not(windows))]
fn update_binary(_force: bool, _nexus_url: &str) -> anyhow::Result<()> {
anyhow::bail!("La commande 'update' n'est disponible que sur Windows (remplacement du binaire + service NSSM)")
}
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
fn uninstall_service() -> anyhow::Result<()> { fn uninstall_service() -> anyhow::Result<()> {
anyhow::bail!("La commande 'uninstall' n'est disponible que sur Windows (service NSSM)") anyhow::bail!("La commande 'uninstall' n'est disponible que sur Windows (service NSSM)")
+46 -4
View File
@@ -6,7 +6,8 @@
//! NSSM doit être présent dans le même dossier que imagod-web.exe //! NSSM doit être présent dans le même dossier que imagod-web.exe
//! ou dans le PATH. Téléchargeable sur https://nssm.cc/download //! ou dans le PATH. Téléchargeable sur https://nssm.cc/download
use std::path::PathBuf; use anyhow::Context;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
/// Trouve l'exécutable NSSM /// Trouve l'exécutable NSSM
@@ -29,7 +30,7 @@ fn own_exe_path() -> anyhow::Result<String> {
.to_string()) .to_string())
} }
pub fn install() -> anyhow::Result<()> { pub fn install(config_override: Option<PathBuf>, api_key: Option<String>) -> anyhow::Result<()> {
if !cfg!(windows) { if !cfg!(windows) {
anyhow::bail!("L'installation en service est disponible uniquement sous Windows"); anyhow::bail!("L'installation en service est disponible uniquement sous Windows");
} }
@@ -49,6 +50,44 @@ pub fn install() -> anyhow::Result<()> {
let exe_path = own_exe_path()?; let exe_path = own_exe_path()?;
let service_name = "imagod-web"; let service_name = "imagod-web";
let exe_dir = Path::new(&exe_path).parent().unwrap_or(Path::new("."));
// --- Config auto-génération ---
let config_path = if let Some(ref cfg) = config_override {
cfg.clone()
} else {
let default_config = exe_dir.join("imagod.toml");
if !default_config.exists() {
let key = api_key.unwrap_or_default();
let config_content = format!(
r#"[imago]
base_url = "http://imago.dracodev.net"
api_key = "{}"
[server]
host = "127.0.0.1"
port = 9199
"#,
key
);
std::fs::write(&default_config, config_content)?;
println!("📝 Config créée : {}", default_config.display());
if key.is_empty() {
println!("⚠️ Clé API non fournie — éditez imagod.toml pour ajouter votre clé Imago");
}
}
default_config
};
// Vérifier que le fichier de config existe
if !config_path.exists() {
anyhow::bail!(
"Fichier de configuration introuvable : {}\n\
Créez un fichier imagod.toml ou utilisez --config <chemin>",
config_path.display()
);
}
println!("🔧 Installation du service Windows '{}'...", service_name); println!("🔧 Installation du service Windows '{}'...", service_name);
println!(" Binaire : {}", exe_path); println!(" Binaire : {}", exe_path);
println!(" NSSM : {}", nssm.display()); println!(" NSSM : {}", nssm.display());
@@ -70,9 +109,12 @@ pub fn install() -> anyhow::Result<()> {
} }
// Configurer le service // Configurer le service
let config_arg = format!("serve --config {}", config_path.display());
let settings: &[(&[&str], &str)] = &[ let settings: &[(&[&str], &str)] = &[
// Commande : "serve" (watcher + dashboard) // Commande : "serve --config <path>"
(&["set", service_name, "AppParameters", "serve"], "Paramètres de l'application"), (&["set", service_name, "AppParameters", &config_arg], "Paramètres de l'application"),
// Répertoire de travail = là où est le binaire
(&["set", service_name, "AppDirectory", &exe_dir.to_string_lossy()], "Répertoire de travail"),
// Démarrage automatique // Démarrage automatique
(&["set", service_name, "Start", "SERVICE_AUTO_START"], "Type de démarrage"), (&["set", service_name, "Start", "SERVICE_AUTO_START"], "Type de démarrage"),
// Nom affiché // Nom affiché
+110
View File
@@ -0,0 +1,110 @@
//! Module de mise à jour automatique depuis Nexus (Windows uniquement)
#[cfg(windows)]
mod imp {
use std::path::Path;
use std::process::Command;
pub fn check_and_update(force: bool, nexus_url: &str) -> anyhow::Result<()> {
let current_version = env!("CARGO_PKG_VERSION");
let platform = "windows";
let exe_path = std::env::current_exe()?;
let exe_dir = exe_path.parent().unwrap_or(Path::new("."));
// 1. Récupérer la version distante
let version_url = format!(
"{}/repository/raw-hosted/imagod-web/{}/latest/version.txt",
nexus_url.trim_end_matches('/'),
platform
);
println!("🔍 Vérification de la version sur Nexus...");
println!(" Actuelle : v{}", current_version);
let remote_version = match reqwest::blocking::get(&version_url) {
Ok(resp) if resp.status().is_success() => resp.text()?.trim().to_string(),
Ok(resp) => anyhow::bail!("Impossible de récupérer la version distante (HTTP {})", resp.status()),
Err(e) => anyhow::bail!("Erreur réseau : {}", e),
};
println!(" Distante : v{}", remote_version);
if !force && remote_version == current_version {
println!("✅ Vous utilisez déjà la dernière version.");
return Ok(());
}
println!("📦 Mise à jour : v{} → v{}", current_version, remote_version);
// 2. Télécharger
let bin_name = "imagod-web.exe";
let bin_url = format!(
"{}/repository/raw-hosted/imagod-web/{}/latest/{}",
nexus_url.trim_end_matches('/'), platform, bin_name
);
println!("⬇️ Téléchargement...");
let response = reqwest::blocking::get(&bin_url)?;
if !response.status().is_success() {
anyhow::bail!("Téléchargement échoué (HTTP {})", response.status());
}
let new_binary = response.bytes()?;
println!(" Téléchargé : {:.1} MB", new_binary.len() as f64 / 1_048_576.0);
// 3. Vérifier SHA256
let sha_url = format!("{}.sha256", bin_url);
let expected_sha = reqwest::blocking::get(&sha_url)?
.text()?
.split_whitespace()
.next()
.unwrap_or("")
.to_string();
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(&new_binary);
let actual_sha = format!("{:x}", hasher.finalize());
if expected_sha.is_empty() {
println!("⚠️ Pas de checksum — vérification ignorée.");
} else if actual_sha != expected_sha {
anyhow::bail!("❌ SHA256 mismatch!\n Attendu: {}\n Obtenu: {}", expected_sha, actual_sha);
} else {
println!("✅ SHA256 vérifié");
}
// 4. Remplacer
let temp_path = exe_dir.join(format!("{}.new", bin_name));
std::fs::write(&temp_path, &new_binary)?;
let _ = Command::new("nssm").args(["stop", "imagod-web"]).status();
std::thread::sleep(std::time::Duration::from_secs(2));
let backup_path = exe_dir.join(format!("{}.old", bin_name));
let _ = std::fs::remove_file(&backup_path);
std::fs::rename(&exe_path, &backup_path)?;
std::fs::rename(&temp_path, &exe_path)?;
// 5. Redémarrer
let start = Command::new("nssm").args(["start", "imagod-web"]).status();
match start {
Ok(s) if s.success() => println!("🚀 Service redémarré !"),
_ => println!("⚠️ Redémarrage manuel requis : nssm start imagod-web"),
}
println!("🎉 Mise à jour v{} → v{} terminée !", current_version, remote_version);
Ok(())
}
}
#[cfg(not(windows))]
mod imp {
pub fn check_and_update(_force: bool, _nexus_url: &str) -> anyhow::Result<()> {
anyhow::bail!("La commande 'update' n'est disponible que sur Windows")
}
}
pub fn check_and_update(force: bool, nexus_url: &str) -> anyhow::Result<()> {
imp::check_and_update(force, nexus_url)
}