Skip to content

Commit

Permalink
Merge pull request #52 from hnez/update-poll-consent
Browse files Browse the repository at this point in the history
Ask the user for consent before polling for updates online
  • Loading branch information
hnez authored Jan 11, 2024
2 parents 5445b24 + e926b6d commit d44f5ca
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 7 deletions.
3 changes: 2 additions & 1 deletion demo_files/srv/tacd/state.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"format_version": 1,
"persistent_topics": {
"/v1/tac/display/show_help": false,
"/v1/tac/setup_mode": false
"/v1/tac/setup_mode": false,
"/v1/tac/update/enable_polling": true
}
}
15 changes: 15 additions & 0 deletions openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,21 @@ paths:
schema:
type: number

/v1/tac/update/enable_polling:
put:
summary: Enable periodic polling for operating system updates
tags: [Updating]
requestBody:
content:
application/json:
schema:
type: boolean
responses:
'204':
description: Polling for OS updates was enabled/disabled
'400':
description: The value could not be parsed as boolean

/v1/tac/update/operation:
get:
summary: Get the currently running system update operation
Expand Down
12 changes: 12 additions & 0 deletions src/broker/topic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,18 @@ impl<E: Serialize + DeserializeOwned + Clone + PartialEq> Topic<E> {

self.modify(|prev| if prev != msg { msg } else { None });
}

/// Wait until the topic is set to the specified value
pub async fn wait_for(self: &Arc<Self>, val: E) {
let (mut stream, sub) = self.clone().subscribe_unbounded();

// Unwrap here to keep the interface simple. The stream could only yield
// None if the sender side is dropped, which will not happen as we hold
// an Arc to self which contains the senders vec.
while stream.next().await.unwrap() != val {}

sub.unsubscribe()
}
}

impl<E: Serialize + DeserializeOwned + Clone + Not + Not<Output = E>> Topic<E> {
Expand Down
18 changes: 18 additions & 0 deletions src/dbus/rauc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ pub struct Rauc {
pub channels: Arc<Topic<Vec<Channel>>>,
pub reload: Arc<Topic<bool>>,
pub should_reboot: Arc<Topic<bool>>,
pub enable_polling: Arc<Topic<bool>>,
}

fn compare_versions(v1: &str, v2: &str) -> Option<Ordering> {
Expand Down Expand Up @@ -178,6 +179,7 @@ fn would_reboot_into_other_slot(slot_status: &SlotStatus, primary: Option<String

async fn channel_polling_task(
conn: Arc<Connection>,
enable_polling: Arc<Topic<bool>>,
channels: Arc<Topic<Vec<Channel>>>,
slot_status: Arc<Topic<Arc<SlotStatus>>>,
name: String,
Expand All @@ -188,6 +190,10 @@ async fn channel_polling_task(
.try_get()
.and_then(|chs| chs.into_iter().find(|ch| ch.name == name))
{
// Make sure update polling is enabled before doing anything,
// as contacting the update server requires user consent.
enable_polling.wait_for(true).await;

let polling_interval = channel.polling_interval;
let slot_status = slot_status.try_get();

Expand Down Expand Up @@ -224,6 +230,7 @@ async fn channel_polling_task(
async fn channel_list_update_task(
conn: Arc<Connection>,
mut reload_stream: Receiver<bool>,
enable_polling: Arc<Topic<bool>>,
channels: Arc<Topic<Vec<Channel>>>,
slot_status: Arc<Topic<Arc<SlotStatus>>>,
) {
Expand Down Expand Up @@ -266,6 +273,7 @@ async fn channel_list_update_task(
for name in names.into_iter() {
let polling_task = spawn(channel_polling_task(
conn.clone(),
enable_polling.clone(),
channels.clone(),
slot_status.clone(),
name,
Expand All @@ -290,6 +298,14 @@ impl Rauc {
channels: bb.topic_ro("/v1/tac/update/channels", None),
reload: bb.topic_wo("/v1/tac/update/channels/reload", Some(true)),
should_reboot: bb.topic_ro("/v1/tac/update/should_reboot", Some(false)),
enable_polling: bb.topic(
"/v1/tac/update/enable_polling",
true,
true,
true,
Some(false),
1,
),
}
}

Expand All @@ -306,6 +322,7 @@ impl Rauc {
spawn(channel_list_update_task(
Arc::new(Connection),
reload_stream,
inst.enable_polling.clone(),
inst.channels.clone(),
inst.slot_status.clone(),
));
Expand Down Expand Up @@ -488,6 +505,7 @@ impl Rauc {
spawn(channel_list_update_task(
conn.clone(),
reload_stream,
inst.enable_polling.clone(),
inst.channels.clone(),
inst.slot_status.clone(),
));
Expand Down
1 change: 1 addition & 0 deletions src/digital_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use async_std::task::spawn;
use crate::broker::{BrokerBuilder, Topic};
use crate::led::BlinkPattern;

#[allow(clippy::items_after_test_module)]
#[cfg(test)]
mod gpio {
mod test;
Expand Down
2 changes: 1 addition & 1 deletion src/ui/screens/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ impl ActivatableScreen for SetupScreen {
spawn(async move {
while let Some(ips) = ip_stream.next().await {
connectivity_topic_task.modify(|prev| {
let ip = ips.get(0).cloned();
let ip = ips.first().cloned();

match (prev.unwrap(), ip) {
(Connectivity::Nothing, Some(ip)) | (Connectivity::IpOnly(_), Some(ip)) => {
Expand Down
2 changes: 1 addition & 1 deletion src/ui/screens/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ impl ActivatableScreen for SystemScreen {
display,
row_anchor(3),
Box::new(|ips: &Vec<String>| {
let ip = ips.get(0).map(|s| s.as_str()).unwrap_or("-");
let ip = ips.first().map(|s| s.as_str()).unwrap_or("-");
format!("IP: {}", ip)
}),
)
Expand Down
33 changes: 33 additions & 0 deletions web/src/Setup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import Spinner from "@cloudscape-design/components/spinner";
import Wizard from "@cloudscape-design/components/wizard";

import { LabgridService, LabgridConfig } from "./SettingsLabgrid";
import { MqttToggle } from "./MqttComponents";
import { ConfigEditor } from "./ConfigEditor";
import { useMqttState } from "./mqtt";

Expand Down Expand Up @@ -130,6 +131,38 @@ export default function Setup() {
</Container>
),
},
{
title: "Configure Software Updates",
description:
"Choose when and how to check for new software releases",
content: (
<Container>
<Box variant="p">
The LXA TAC uses <Link href="https://rauc.io/">RAUC</Link>{" "}
to manage and install software updates. A RAUC software
update updates all of the installed software components at
once and falls back to the previous version if something
went wrong.
</Box>
<Box variant="p">
We continually improve the software experience on the TAC
and ship new features, so make sure to stay up to date
with new releases.
</Box>
<Box variant="p">
Would you like the TAC to periodically connect to the
update server and check for software updates? You will be
notified about updates via the LXA TACs display and the
web interface and can start the update process from there.
</Box>
<Box variant="p" padding="s">
<MqttToggle topic="/v1/tac/update/enable_polling">
Periodically check for updates
</MqttToggle>
</Box>
</Container>
),
},
{
title: "Add SSH keys",
description:
Expand Down
48 changes: 44 additions & 4 deletions web/src/TacComponents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import SpaceBetween from "@cloudscape-design/components/space-between";
import Spinner from "@cloudscape-design/components/spinner";
import Table from "@cloudscape-design/components/table";

import { MqttButton } from "./MqttComponents";
import { MqttButton, MqttToggle } from "./MqttComponents";
import { useMqttSubscription } from "./mqtt";

type RootfsSlot = {
Expand Down Expand Up @@ -221,12 +221,41 @@ export function SlotStatus() {
}
}

export function UpdateConfig() {
return (
<Container
header={
<Header
variant="h3"
description="Decide how updates are handled on this TAC"
>
Update Configuration
</Header>
}
>
<ColumnLayout columns={3} variant="text-grid">
<Box>
<Box variant="awsui-key-label">Update Polling</Box>
<MqttToggle topic="/v1/tac/update/enable_polling">
Periodically check for updates
</MqttToggle>
</Box>
</ColumnLayout>
</Container>
);
}

export function UpdateChannels() {
const channels_topic = useMqttSubscription<Array<Channel>>(
"/v1/tac/update/channels",
);
const enable_polling_topic = useMqttSubscription<Array<Channel>>(
"/v1/tac/update/enable_polling",
);

const channels = channels_topic !== undefined ? channels_topic : [];
const enable_polling =
enable_polling_topic !== undefined ? enable_polling_topic : false;

return (
<Table
Expand Down Expand Up @@ -260,7 +289,9 @@ export function UpdateChannels() {
{
id: "enabled",
header: "Enabled",
cell: (e) => <Checkbox checked={e.enabled} />,
cell: (e) => (
<Checkbox checked={e.enabled} disabled={!enable_polling} />
),
},
{
id: "description",
Expand All @@ -276,8 +307,12 @@ export function UpdateChannels() {
},
{
id: "interval",
header: "Update Interval",
header: "Polling Interval",
cell: (e) => {
if (!enable_polling) {
return "Disabled";
}

if (!e.polling_interval) {
return "Never";
}
Expand Down Expand Up @@ -313,7 +348,11 @@ export function UpdateChannels() {
}

if (!e.bundle) {
return <Spinner />;
if (enable_polling) {
return <Spinner />;
} else {
return "Polling disabled";
}
}

if (!e.bundle.newer_than_installed) {
Expand Down Expand Up @@ -435,6 +474,7 @@ export function UpdateContainer() {
}
>
<SpaceBetween size="m">
<UpdateConfig />
<UpdateChannels />
<SlotStatus />
</SpaceBetween>
Expand Down

0 comments on commit d44f5ca

Please sign in to comment.