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

View File

@@ -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

12
pilot-v2/Cargo.lock generated
View File

@@ -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",

View File

@@ -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"

View File

@@ -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

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");
}
}

44
scripts/test_command.sh Executable file
View File

@@ -0,0 +1,44 @@
#!/bin/bash
# Script simple pour tester les commandes MQTT avec Pilot v2
# Usage: ./test_command.sh <action> <value>
# Exemple: ./test_command.sh screen OFF
MQTT_HOST="10.0.0.3"
MQTT_PORT="1883"
DEVICE="asus"
if [ $# -ne 2 ]; then
echo "Usage: $0 <action> <value>"
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