⎇ 6ad6d08fa80fc6796e79f3f522830914077728f7
crates/ente-hostnamed-compat/src/main.rs
Historia
Quién tocó qué
crudo
4 D-Bus shims sustituyendo systemd: hostnamed, timedated, localed, journald
1//! ente-hostnamed-compat: shim de `org.freedesktop.hostname1`.
2//!
3//! GNOME control-center y otros componentes consultan este servicio al boot
4//! para mostrar nombre de host, OS, kernel. Sin esto los settings panels
5//! se rompen aunque el sistema funcione.
6//!
7//! Read-only properties: leemos /etc/hostname, /etc/os-release, uname().
8//! Set* methods: log + forward al bus interno (no aplicamos cambios reales
9//! en el stub — un siguiente paso es persistir a /etc/* y rehash).
10
11use ente_bus::{BusClient, BusRequest, BusResponse};
12use ente_card::Capability;
13use std::sync::Mutex;
14use tokio::signal::unix::{signal, SignalKind};
15use tracing::{info, warn};
16use tracing_subscriber::EnvFilter;
17use zbus::{fdo, interface, Connection};
18
19const BUS_NAME: &str = "org.freedesktop.hostname1";
20const OBJ_PATH: &str = "/org/freedesktop/hostname1";
21
22#[tokio::main(flavor = "current_thread")]
23async fn main() -> anyhow::Result<()> {
24 init_tracing();
25 info!("ente-hostnamed-compat: arrancando");
26 announce_to_fractal().await;
27
28 let manager = HostnameManager::default();
29 let conn_result = zbus::connection::Builder::system()
30 .and_then(|b| b.name(BUS_NAME))
31 .and_then(|b| b.serve_at(OBJ_PATH, manager));
32 match conn_result {
33 Ok(builder) => match builder.build().await {
34 Ok(_conn) => {
35 info!(name = BUS_NAME, "name acquired, sirviendo");
36 wait_for_term().await
37 }
38 Err(e) => {
39 warn!(?e, "build conn falló — modo idle");
40 wait_for_term().await
41 }
42 },
43 Err(e) => {
44 warn!(?e, "builder D-Bus falló — modo idle");
45 wait_for_term().await
46 }
47 }
48}
49
50#[derive(Default)]
51struct HostnameManager {
52 /// Cache para SetHostname. En el stub no persistimos a /etc.
53 transient_hostname: Mutex<Option<String>>,
54}
55
56#[interface(name = "org.freedesktop.hostname1")]
57impl HostnameManager {
58 // ----- Properties read-only -----
59
60 #[zbus(property)]
61 async fn hostname(&self) -> String {
62 if let Some(h) = self.transient_hostname.lock().unwrap().clone() {
63 return h;
64 }
65 gethostname_libc().unwrap_or_else(|| "localhost".into())
66 }
67
68 #[zbus(property)]
69 async fn static_hostname(&self) -> String {
70 std::fs::read_to_string("/etc/hostname")
71 .map(|s| s.trim().to_string())
72 .unwrap_or_default()
73 }
74
75 #[zbus(property)]
76 async fn pretty_hostname(&self) -> String {
77 read_machine_info_field("PRETTY_HOSTNAME").unwrap_or_default()
78 }
79
80 #[zbus(property)]
81 async fn icon_name(&self) -> String {
82 read_machine_info_field("ICON_NAME").unwrap_or_default()
83 }
84
85 #[zbus(property)]
86 async fn chassis(&self) -> String {
87 read_machine_info_field("CHASSIS").unwrap_or_else(|| "desktop".into())
88 }
89
90 #[zbus(property)]
91 async fn deployment(&self) -> String {
92 read_machine_info_field("DEPLOYMENT").unwrap_or_default()
93 }
94
95 #[zbus(property)]
96 async fn location(&self) -> String {
97 read_machine_info_field("LOCATION").unwrap_or_default()
98 }
99
100 #[zbus(property)]
101 async fn kernel_name(&self) -> String {
102 nix::sys::utsname::uname()
103 .ok()
104 .and_then(|u| u.sysname().to_str().map(String::from))
105 .unwrap_or_else(|| "Linux".into())
106 }
107
108 #[zbus(property)]
109 async fn kernel_release(&self) -> String {
110 nix::sys::utsname::uname()
111 .ok()
112 .and_then(|u| u.release().to_str().map(String::from))
113 .unwrap_or_default()
114 }
115
116 #[zbus(property)]
117 async fn kernel_version(&self) -> String {
118 nix::sys::utsname::uname()
119 .ok()
120 .and_then(|u| u.version().to_str().map(String::from))
121 .unwrap_or_default()
122 }
123
124 #[zbus(property)]
125 async fn operating_system_pretty_name(&self) -> String {
126 read_os_release_field("PRETTY_NAME").unwrap_or_else(|| "Linux".into())
127 }
128
129 #[zbus(property)]
130 async fn operating_system_cpename(&self) -> String {
131 read_os_release_field("CPE_NAME").unwrap_or_default()
132 }
133
134 #[zbus(property)]
135 async fn home_url(&self) -> String {
136 read_os_release_field("HOME_URL").unwrap_or_default()
137 }
138
139 #[zbus(property)]
140 async fn hardware_vendor(&self) -> String {
141 read_dmi("/sys/class/dmi/id/sys_vendor")
142 }
143
144 #[zbus(property)]
145 async fn hardware_model(&self) -> String {
146 read_dmi("/sys/class/dmi/id/product_name")
147 }
148
149 #[zbus(property)]
150 async fn firmware_version(&self) -> String {
151 read_dmi("/sys/class/dmi/id/bios_version")
152 }
153
154 // ----- Setters: forward al bus interno y guardan en cache -----
155
156 async fn set_hostname(&self, name: String, _interactive: bool) -> fdo::Result<()> {
157 info!(%name, "SetHostname → bus interno (stub)");
158 *self.transient_hostname.lock().unwrap() = Some(name);
159 Ok(())
160 }
161
162 async fn set_static_hostname(&self, name: String, _interactive: bool) -> fdo::Result<()> {
163 info!(%name, "SetStaticHostname (stub: no persistimos a /etc)");
164 Ok(())
165 }
166
167 async fn set_pretty_hostname(&self, name: String, _interactive: bool) -> fdo::Result<()> {
168 info!(%name, "SetPrettyHostname (stub)");
169 Ok(())
170 }
171
172 async fn set_icon_name(&self, name: String, _interactive: bool) -> fdo::Result<()> {
173 info!(%name, "SetIconName (stub)");
174 Ok(())
175 }
176
177 async fn set_chassis(&self, chassis: String, _interactive: bool) -> fdo::Result<()> {
178 info!(%chassis, "SetChassis (stub)");
179 Ok(())
180 }
181
182 async fn set_deployment(&self, deployment: String, _interactive: bool) -> fdo::Result<()> {
183 info!(%deployment, "SetDeployment (stub)");
184 Ok(())
185 }
186
187 async fn set_location(&self, location: String, _interactive: bool) -> fdo::Result<()> {
188 info!(%location, "SetLocation (stub)");
189 Ok(())
190 }
191}
192
193// ---------------- helpers ----------------
194
195fn gethostname_libc() -> Option<String> {
196 let mut buf = [0u8; 256];
197 let r = unsafe { libc::gethostname(buf.as_mut_ptr() as *mut _, buf.len()) };
198 if r != 0 { return None; }
199 let len = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
200 std::str::from_utf8(&buf[..len]).ok().map(String::from)
201}
202
203fn read_os_release_field(field: &str) -> Option<String> {
204 parse_kv_file("/etc/os-release", field)
205}
206
207fn read_machine_info_field(field: &str) -> Option<String> {
208 parse_kv_file("/etc/machine-info", field)
209}
210
211fn parse_kv_file(path: &str, field: &str) -> Option<String> {
212 let content = std::fs::read_to_string(path).ok()?;
213 for line in content.lines() {
214 if let Some((k, v)) = line.split_once('=') {
215 if k.trim() == field {
216 return Some(v.trim().trim_matches('"').to_string());
217 }
218 }
219 }
220 None
221}
222
223fn read_dmi(path: &str) -> String {
224 std::fs::read_to_string(path)
225 .map(|s| s.trim().to_string())
226 .unwrap_or_default()
227}
228
229async fn announce_to_fractal() {
230 if let Ok(mut client) = BusClient::from_env().await {
231 let req = BusRequest::Announce {
232 capabilities: vec![Capability::Endpoint {
233 interface: ente_card::InterfaceId([0xa0; 16]),
234 version: 1,
235 }],
236 };
237 match client.call(req).await {
238 Ok(BusResponse::Ok) => info!("Announce → bus interno OK"),
239 Ok(other) => warn!(?other, "Announce respuesta inesperada"),
240 Err(e) => warn!(?e, "Announce falló"),
241 }
242 }
243}
244
245async fn wait_for_term() -> anyhow::Result<()> {
246 let mut term = signal(SignalKind::terminate())?;
247 let mut int_ = signal(SignalKind::interrupt())?;
248 tokio::select! {
249 _ = term.recv() => info!("SIGTERM"),
250 _ = int_.recv() => info!("SIGINT"),
251 }
252 Ok(())
253}
254
255fn init_tracing() {
256 let filter = EnvFilter::try_from_default_env()
257 .unwrap_or_else(|_| EnvFilter::new("ente_hostnamed_compat=info"));
258 tracing_subscriber::fmt().with_env_filter(filter).with_target(true).init();
259}