From 1640754df8841f83b7e32b23d82319dc39935ab2 Mon Sep 17 00:00:00 2001 From: Gilles Soulier Date: Tue, 30 Dec 2025 06:52:41 +0100 Subject: [PATCH] Add $hostname variable substitution in config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds support for automatic hostname substitution in configuration files. Users can now use $hostname in device.name, device.identifiers, and mqtt.client_id to automatically use the system's hostname. Changes: - Add hostname crate dependency (v0.4) - Implement expand_variables() to replace $hostname with actual hostname - Add get_hostname() helper function - Update config.example.yaml to demonstrate $hostname usage - Add test for hostname substitution - Update config.yaml to use $hostname by default - Add test_command.sh script for testing MQTT commands This makes deployment easier across multiple machines without manual config changes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- config/config.example.yaml | 10 +++-- pilot-v2/Cargo.lock | 12 ++++++ pilot-v2/Cargo.toml | 1 + pilot-v2/config.yaml | 8 ++-- pilot-v2/src/config/mod.rs | 86 +++++++++++++++++++++++++++++++++++++- scripts/test_command.sh | 44 +++++++++++++++++++ 6 files changed, 153 insertions(+), 8 deletions(-) create mode 100755 scripts/test_command.sh diff --git a/config/config.example.yaml b/config/config.example.yaml index b0da6a3..5d516ba 100644 --- a/config/config.example.yaml +++ b/config/config.example.yaml @@ -1,7 +1,11 @@ # Codex created 2025-12-29_0224 +# Configuration example for Pilot v2 +# Special variables: +# $hostname - Will be replaced by the system hostname at runtime + device: - name: pilot-device - identifiers: ["pilot-device"] + name: $hostname # Use $hostname for automatic hostname, or a custom name like "my-pc" + identifiers: ["$hostname"] mqtt: host: "127.0.0.1" @@ -10,7 +14,7 @@ mqtt: password: "" base_topic: "pilot" discovery_prefix: "homeassistant" - client_id: "pilot-device" + client_id: "$hostname" # MQTT client ID - use $hostname or a custom ID keepalive_s: 60 qos: 0 retain_states: true diff --git a/pilot-v2/Cargo.lock b/pilot-v2/Cargo.lock index 851b5f1..cf07a22 100644 --- a/pilot-v2/Cargo.lock +++ b/pilot-v2/Cargo.lock @@ -672,6 +672,17 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hostname" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd" +dependencies = [ + "cfg-if", + "libc", + "windows-link", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -930,6 +941,7 @@ name = "pilot-v2" version = "0.1.0" dependencies = [ "anyhow", + "hostname", "local-ip-address", "rumqttc", "serde", diff --git a/pilot-v2/Cargo.toml b/pilot-v2/Cargo.toml index da3adb2..2871fd7 100644 --- a/pilot-v2/Cargo.toml +++ b/pilot-v2/Cargo.toml @@ -17,3 +17,4 @@ tokio = { version = "1", features = ["rt-multi-thread", "macros", "time", "signa sysinfo = "0.30" local-ip-address = "0.6" zbus = "3" +hostname = "0.4" diff --git a/pilot-v2/config.yaml b/pilot-v2/config.yaml index b0da6a3..1e68850 100644 --- a/pilot-v2/config.yaml +++ b/pilot-v2/config.yaml @@ -1,16 +1,16 @@ # Codex created 2025-12-29_0224 device: - name: pilot-device - identifiers: ["pilot-device"] + name: $hostname + identifiers: ["$hostname"] mqtt: - host: "127.0.0.1" + host: "10.0.0.3" port: 1883 username: "" password: "" base_topic: "pilot" discovery_prefix: "homeassistant" - client_id: "pilot-device" + client_id: "$hostname" keepalive_s: 60 qos: 0 retain_states: true diff --git a/pilot-v2/src/config/mod.rs b/pilot-v2/src/config/mod.rs index 3ed50d0..c338df1 100644 --- a/pilot-v2/src/config/mod.rs +++ b/pilot-v2/src/config/mod.rs @@ -87,8 +87,12 @@ pub fn load() -> Result { if path.exists() { let raw = fs::read_to_string(&path) .with_context(|| format!("failed reading config {}", path.display()))?; - let cfg: Config = serde_yaml::from_str(&raw) + let mut cfg: Config = serde_yaml::from_str(&raw) .with_context(|| format!("failed parsing config {}", path.display()))?; + + // Substituer $hostname par le vrai hostname + expand_variables(&mut cfg)?; + validate(&cfg)?; return Ok(cfg); } @@ -116,6 +120,39 @@ pub fn base_device_topic(cfg: &Config) -> String { format!("{}/{}", base, cfg.device.name) } +// Obtient le hostname du système +fn get_hostname() -> Result { + let hostname = hostname::get() + .context("failed to get system hostname")? + .to_string_lossy() + .to_string(); + Ok(hostname) +} + +// Remplace les variables comme $hostname dans la config +fn expand_variables(cfg: &mut Config) -> Result<()> { + let hostname = get_hostname()?; + + // Remplacer dans device.name + if cfg.device.name == "$hostname" { + cfg.device.name = hostname.clone(); + } + + // Remplacer dans device.identifiers + for identifier in &mut cfg.device.identifiers { + if identifier == "$hostname" { + *identifier = hostname.clone(); + } + } + + // Remplacer dans client_id + if cfg.mqtt.client_id == "$hostname" { + cfg.mqtt.client_id = hostname.clone(); + } + + Ok(()) +} + // Verifie les champs minimum pour eviter les erreurs au demarrage. fn validate(cfg: &Config) -> Result<()> { if cfg.device.name.trim().is_empty() { @@ -180,4 +217,51 @@ publish: assert_eq!(cfg.mqtt.port, 1883); assert!(cfg.features.commands.dry_run); } + + #[test] + fn hostname_substitution() { + let raw = r#" +device: + name: "$hostname" + identifiers: ["$hostname"] +mqtt: + host: "127.0.0.1" + port: 1883 + username: "" + password: "" + base_topic: "pilot" + discovery_prefix: "homeassistant" + client_id: "$hostname" + keepalive_s: 60 + qos: 0 + retain_states: true +features: + telemetry: + enabled: true + interval_s: 5 + commands: + enabled: true + cooldown_s: 2 + dry_run: true + allowlist: ["shutdown"] +power_backend: + linux: "linux_logind_polkit" + windows: "windows_service" +screen_backend: + linux: "gnome_busctl" + windows: "winapi_session" +publish: + heartbeat_s: 10 + availability: true +"#; + + let mut cfg: Config = serde_yaml::from_str(raw).unwrap(); + expand_variables(&mut cfg).unwrap(); + + let hostname = get_hostname().unwrap(); + assert_eq!(cfg.device.name, hostname); + assert_eq!(cfg.device.identifiers[0], hostname); + assert_eq!(cfg.mqtt.client_id, hostname); + assert_ne!(cfg.device.name, "$hostname"); + } } diff --git a/scripts/test_command.sh b/scripts/test_command.sh new file mode 100755 index 0000000..8fbb92c --- /dev/null +++ b/scripts/test_command.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# Script simple pour tester les commandes MQTT avec Pilot v2 +# Usage: ./test_command.sh +# Exemple: ./test_command.sh screen OFF + +MQTT_HOST="10.0.0.3" +MQTT_PORT="1883" +DEVICE="asus" + +if [ $# -ne 2 ]; then + echo "Usage: $0 " + echo "Actions: shutdown, reboot, sleep, screen" + echo "Values: ON, OFF" + echo "Exemple: $0 screen OFF" + exit 1 +fi + +ACTION=$1 +VALUE=$2 +TOPIC="pilot/${DEVICE}/cmd/${ACTION}/set" + +# Vérifier si mosquitto_pub est installé +if ! command -v mosquitto_pub &> /dev/null; then + echo "❌ mosquitto_pub n'est pas installé" + echo "Installer avec: sudo apt install mosquitto-clients" + exit 1 +fi + +echo "📡 Envoi commande MQTT:" +echo " Topic: $TOPIC" +echo " Message: $VALUE" +echo "" + +mosquitto_pub -h "$MQTT_HOST" -p "$MQTT_PORT" -t "$TOPIC" -m "$VALUE" + +if [ $? -eq 0 ]; then + echo "✅ Commande envoyée avec succès!" + echo "" + echo "Vérifiez les logs de Pilot pour voir le résultat:" + echo " En dry-run mode, la commande sera loggée mais pas exécutée" +else + echo "❌ Erreur lors de l'envoi de la commande" + exit 1 +fi