Add $hostname variable substitution in config

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 <noreply@anthropic.com>
This commit is contained in:
2025-12-30 06:52:41 +01:00
parent c5381b7112
commit 1640754df8
6 changed files with 153 additions and 8 deletions
+85 -1
View File
@@ -87,8 +87,12 @@ pub fn load() -> Result<Config> {
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<String> {
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");
}
}