Skip to content

dbus: rauc: replace tacd-based update polling with native update polling #90

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ac2e0c4
tacd: dbus: rauc: re-introspect services
hnez Mar 27, 2025
77aafa2
dbus: systemd: also monitor rauc.service
hnez Apr 2, 2025
a164327
dbus: rauc: add Channels type to use instead of Vec<Channel>
hnez Apr 2, 2025
0833495
dbus: rauc: use a UpdateRequest object instead of a simple URL for in…
hnez Apr 2, 2025
b1d8baa
dbus: rauc: allow restricting installation to a specific manifest_hash
hnez Apr 2, 2025
46cb875
dbus: rauc: remove tacd-based update polling
hnez Apr 2, 2025
6a43333
dbus: rauc: update_channels: add a concept of a single primary channel
hnez Apr 2, 2025
42b959f
dbus: rauc: only install bundles from the primary channel
hnez Apr 2, 2025
2b84818
dbus: rauc: system_conf: write runtime RAUC config with poll section
hnez Apr 2, 2025
008b883
dbus: rauc: reload rauc daemon when required
hnez Apr 2, 2025
632685f
dbus: rauc: trigger a single poll for updates after reloading the daemon
hnez Apr 3, 2025
0ab21a3
dbus: rauc: forward poller status to broker
hnez Apr 2, 2025
911e489
dbus: rauc: add manifest_hash und effective_url to UpstreamBundle
hnez Apr 2, 2025
f6d6b46
dbus: rauc: add support for enabling the auto install feature
hnez Apr 2, 2025
79f970f
dbus: rauc: implement forced polling via update channel files
hnez Mar 31, 2025
e5c3a3c
dbus: rauc: allow configuring the *_criteria in channel files
hnez Mar 31, 2025
31dd189
dbus: rauc: prevent auto updates while in setup mode
hnez Apr 2, 2025
cb89591
web: setup: inform the user about additional headers that are sent now
hnez Apr 2, 2025
9f5d256
web: indicate to the user when a channel is enabled but not primary
hnez Apr 2, 2025
84c6b9e
web: add toggle switches to enable automatic installation of updates
hnez Apr 2, 2025
4e711b3
web: use manifest_hash and effective_url when triggering an install
hnez Apr 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 35 additions & 14 deletions src/dbus/rauc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@
use std::collections::HashMap;

use anyhow::Result;
use async_std::channel::Receiver;
use async_std::stream::StreamExt;
use async_std::sync::Arc;
use futures_util::FutureExt;
use log::warn;
use serde::{Deserialize, Serialize};

Expand All @@ -31,6 +31,9 @@ use crate::watched_tasks::WatchedTasksBuilder;
mod update_channels;
pub use update_channels::{Channel, Channels};

mod system_conf;
use system_conf::update_system_conf;

#[cfg(feature = "demo_mode")]
mod demo_mode;

Expand Down Expand Up @@ -119,7 +122,6 @@ pub struct Rauc {
pub channels: Arc<Topic<Channels>>,
pub reload: Arc<Topic<bool>>,
pub should_reboot: Arc<Topic<bool>>,
#[allow(dead_code)]
pub enable_polling: Arc<Topic<bool>>,
}

Expand Down Expand Up @@ -183,13 +185,26 @@ fn would_reboot_into_other_slot(slot_status: &SlotStatus, primary: Option<String
}

async fn channel_list_update_task(
mut reload_stream: Receiver<bool>,
reload: Arc<Topic<bool>>,
enable_polling: Arc<Topic<bool>>,
channels: Arc<Topic<Channels>>,
) -> Result<()> {
while let Some(reload) = reload_stream.next().await {
if !reload {
continue;
}
let (reload_stream, _) = reload.subscribe_unbounded();
let (mut enable_polling_stream, _) = enable_polling.subscribe_unbounded();

let mut enable_polling = enable_polling_stream.next().await.unwrap_or(false);

'reload_loop: loop {
futures::select! {
reload = reload_stream.recv().fuse() => {
if !(reload?) {
continue 'reload_loop
}
}
enable_polling_new = enable_polling_stream.recv().fuse() => {
enable_polling = enable_polling_new?;
}
};

// Read the list of available update channels
let new_channels = match Channels::from_directory(CHANNELS_DIR) {
Expand All @@ -200,10 +215,10 @@ async fn channel_list_update_task(
}
};

update_system_conf(new_channels.primary(), enable_polling)?;

channels.set(new_channels);
}

Ok(())
}

impl Rauc {
Expand Down Expand Up @@ -242,10 +257,13 @@ impl Rauc {
inst.last_error.set("".to_string());

// Reload the channel list on request
let (reload_stream, _) = inst.reload.clone().subscribe_unbounded();
wtb.spawn_task(
"rauc-channel-list-update",
channel_list_update_task(reload_stream, inst.channels.clone()),
channel_list_update_task(
inst.reload.clone(),
inst.enable_polling.clone(),
inst.channels.clone(),
),
)?;

Ok(inst)
Expand Down Expand Up @@ -444,11 +462,14 @@ impl Rauc {
Ok(())
})?;

// Reload the channel list on request
let (reload_stream, _) = inst.reload.clone().subscribe_unbounded();
// Reload the channel list when required
wtb.spawn_task(
"rauc-channel-list-update",
channel_list_update_task(reload_stream, inst.channels.clone()),
channel_list_update_task(
inst.reload.clone(),
inst.enable_polling.clone(),
inst.channels.clone(),
),
)?;

Ok(inst)
Expand Down
120 changes: 120 additions & 0 deletions src/dbus/rauc/system_conf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use std::fmt::Write;
use std::fs::{create_dir_all, read_to_string, remove_file, rename, write};
use std::io::{Error, ErrorKind};
use std::path::Path;

use super::Channel;

use log::info;

#[cfg(feature = "demo_mode")]
mod imports {
pub(super) const STATIC_CONF_PATH: &str = "demo_files/usr/lib/rauc/system.conf";
pub(super) const DYNAMIC_CONF_PATH: &str = "demo_files/run/rauc/system.conf";
}

#[cfg(not(feature = "demo_mode"))]
mod imports {
pub(super) const STATIC_CONF_PATH: &str = "/usr/lib/rauc/system.conf";
pub(super) const DYNAMIC_CONF_PATH: &str = "/run/rauc/system.conf";
}

use imports::*;

const MAGIC_LINE: &str = "\n# <tacd-poll-section>\n";

fn poll_section(
primary_channel: Option<&Channel>,
polling: bool,
) -> Result<Option<String>, std::fmt::Error> {
// If no primary channel is configured or if polling is not enabled,
// then we do not need a `[poll]` section at all.
let primary_channel = match (primary_channel, polling) {
(Some(pc), true) => pc,
_ => return Ok(None),
};

let mut section = String::new();

writeln!(&mut section)?;
writeln!(&mut section, "[poll]")?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've decided to rename the section from poll to polling.

Suggested change
writeln!(&mut section, "[poll]")?;
writeln!(&mut section, "[polling]")?;

Also update the comment above and in update_system_conf().

writeln!(&mut section, "source={}", primary_channel.url)?;

if let Some(interval) = primary_channel.polling_interval {
writeln!(&mut section, "interval-sec={}", interval.as_secs())?;
}

writeln!(&mut section, "candidate-criteria=different-version")?;

Ok(Some(section))
}

pub fn update_system_conf(
primary_channel: Option<&Channel>,
enable_polling: bool,
) -> std::io::Result<bool> {
let dynamic_conf = {
match poll_section(primary_channel, enable_polling) {
Ok(Some(ps)) => {
// We use the config in /etc as a template ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean the one in /usr/lib/rauc/?

let static_conf = read_to_string(STATIC_CONF_PATH)?;

// ... and replace the line `# <tacd-poll-section>` with our
// generated `[poll]` section.
let dc = static_conf.replacen(MAGIC_LINE, &ps, 1);

// The user may have decided not to include a `# <tacd-poll-section>`
// line. In that case we do not need a dynamic config at all.
if dc == static_conf {
info!(
"Rauc config {} did not contain magic line '{}'. Will not generate poll section.",
STATIC_CONF_PATH, MAGIC_LINE
);

None
} else {
Some(dc)
}
}
_ => None,
}
};

/* Do we need a dynamic config in /run/rauc?
*
* If so, then is it actually different from what we already have?
* If not, then there is no need to restart the daemon.
* If it is, we write the new one and signal the need for a daemon
* restart.
*
* If we do not need dynamic config, then try to delete the previous one.
* If there was none, then the daemon does not have to be restarted.
* If there was a dynamic config before, then we need to restart the
* daemon.
*/
match dynamic_conf {
Some(new) => match read_to_string(DYNAMIC_CONF_PATH) {
Ok(old) if old == new => Ok(false),
Err(err) if err.kind() != ErrorKind::NotFound => Err(err),
Ok(_) | Err(_) => {
let dynamic_conf_dir = Path::new(DYNAMIC_CONF_PATH)
.parent()
.ok_or_else(|| Error::other("Invalid dynamic config path"))?;

let tmp_path = dynamic_conf_dir.join("system.conf.tacd-tmp");

create_dir_all(dynamic_conf_dir)?;

write(&tmp_path, &new)?;
rename(&tmp_path, DYNAMIC_CONF_PATH)?;

Ok(true)
}
},
None => match remove_file(DYNAMIC_CONF_PATH) {
Ok(_) => Ok(true),
Err(err) if err.kind() == ErrorKind::NotFound => Ok(false),
Err(err) => Err(err),
},
}
}
1 change: 0 additions & 1 deletion src/dbus/rauc/update_channels.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ impl Channels {
self.0
}

#[cfg(not(feature = "demo_mode"))]
pub(super) fn primary(&self) -> Option<&Channel> {
self.0.iter().find(|ch| ch.primary)
}
Expand Down