Skip to content

Commit d44f5ca

Browse files
authored
Merge pull request #52 from hnez/update-poll-consent
Ask the user for consent before polling for updates online
2 parents 5445b24 + e926b6d commit d44f5ca

File tree

9 files changed

+127
-7
lines changed

9 files changed

+127
-7
lines changed

demo_files/srv/tacd/state.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"format_version": 1,
33
"persistent_topics": {
44
"/v1/tac/display/show_help": false,
5-
"/v1/tac/setup_mode": false
5+
"/v1/tac/setup_mode": false,
6+
"/v1/tac/update/enable_polling": true
67
}
78
}

openapi.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,21 @@ paths:
743743
schema:
744744
type: number
745745

746+
/v1/tac/update/enable_polling:
747+
put:
748+
summary: Enable periodic polling for operating system updates
749+
tags: [Updating]
750+
requestBody:
751+
content:
752+
application/json:
753+
schema:
754+
type: boolean
755+
responses:
756+
'204':
757+
description: Polling for OS updates was enabled/disabled
758+
'400':
759+
description: The value could not be parsed as boolean
760+
746761
/v1/tac/update/operation:
747762
get:
748763
summary: Get the currently running system update operation

src/broker/topic.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,18 @@ impl<E: Serialize + DeserializeOwned + Clone + PartialEq> Topic<E> {
336336

337337
self.modify(|prev| if prev != msg { msg } else { None });
338338
}
339+
340+
/// Wait until the topic is set to the specified value
341+
pub async fn wait_for(self: &Arc<Self>, val: E) {
342+
let (mut stream, sub) = self.clone().subscribe_unbounded();
343+
344+
// Unwrap here to keep the interface simple. The stream could only yield
345+
// None if the sender side is dropped, which will not happen as we hold
346+
// an Arc to self which contains the senders vec.
347+
while stream.next().await.unwrap() != val {}
348+
349+
sub.unsubscribe()
350+
}
339351
}
340352

341353
impl<E: Serialize + DeserializeOwned + Clone + Not + Not<Output = E>> Topic<E> {

src/dbus/rauc.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ pub struct Rauc {
104104
pub channels: Arc<Topic<Vec<Channel>>>,
105105
pub reload: Arc<Topic<bool>>,
106106
pub should_reboot: Arc<Topic<bool>>,
107+
pub enable_polling: Arc<Topic<bool>>,
107108
}
108109

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

179180
async fn channel_polling_task(
180181
conn: Arc<Connection>,
182+
enable_polling: Arc<Topic<bool>>,
181183
channels: Arc<Topic<Vec<Channel>>>,
182184
slot_status: Arc<Topic<Arc<SlotStatus>>>,
183185
name: String,
@@ -188,6 +190,10 @@ async fn channel_polling_task(
188190
.try_get()
189191
.and_then(|chs| chs.into_iter().find(|ch| ch.name == name))
190192
{
193+
// Make sure update polling is enabled before doing anything,
194+
// as contacting the update server requires user consent.
195+
enable_polling.wait_for(true).await;
196+
191197
let polling_interval = channel.polling_interval;
192198
let slot_status = slot_status.try_get();
193199

@@ -224,6 +230,7 @@ async fn channel_polling_task(
224230
async fn channel_list_update_task(
225231
conn: Arc<Connection>,
226232
mut reload_stream: Receiver<bool>,
233+
enable_polling: Arc<Topic<bool>>,
227234
channels: Arc<Topic<Vec<Channel>>>,
228235
slot_status: Arc<Topic<Arc<SlotStatus>>>,
229236
) {
@@ -266,6 +273,7 @@ async fn channel_list_update_task(
266273
for name in names.into_iter() {
267274
let polling_task = spawn(channel_polling_task(
268275
conn.clone(),
276+
enable_polling.clone(),
269277
channels.clone(),
270278
slot_status.clone(),
271279
name,
@@ -290,6 +298,14 @@ impl Rauc {
290298
channels: bb.topic_ro("/v1/tac/update/channels", None),
291299
reload: bb.topic_wo("/v1/tac/update/channels/reload", Some(true)),
292300
should_reboot: bb.topic_ro("/v1/tac/update/should_reboot", Some(false)),
301+
enable_polling: bb.topic(
302+
"/v1/tac/update/enable_polling",
303+
true,
304+
true,
305+
true,
306+
Some(false),
307+
1,
308+
),
293309
}
294310
}
295311

@@ -306,6 +322,7 @@ impl Rauc {
306322
spawn(channel_list_update_task(
307323
Arc::new(Connection),
308324
reload_stream,
325+
inst.enable_polling.clone(),
309326
inst.channels.clone(),
310327
inst.slot_status.clone(),
311328
));
@@ -488,6 +505,7 @@ impl Rauc {
488505
spawn(channel_list_update_task(
489506
conn.clone(),
490507
reload_stream,
508+
inst.enable_polling.clone(),
491509
inst.channels.clone(),
492510
inst.slot_status.clone(),
493511
));

src/digital_io.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use async_std::task::spawn;
2222
use crate::broker::{BrokerBuilder, Topic};
2323
use crate::led::BlinkPattern;
2424

25+
#[allow(clippy::items_after_test_module)]
2526
#[cfg(test)]
2627
mod gpio {
2728
mod test;

src/ui/screens/setup.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ impl ActivatableScreen for SetupScreen {
116116
spawn(async move {
117117
while let Some(ips) = ip_stream.next().await {
118118
connectivity_topic_task.modify(|prev| {
119-
let ip = ips.get(0).cloned();
119+
let ip = ips.first().cloned();
120120

121121
match (prev.unwrap(), ip) {
122122
(Connectivity::Nothing, Some(ip)) | (Connectivity::IpOnly(_), Some(ip)) => {

src/ui/screens/system.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ impl ActivatableScreen for SystemScreen {
117117
display,
118118
row_anchor(3),
119119
Box::new(|ips: &Vec<String>| {
120-
let ip = ips.get(0).map(|s| s.as_str()).unwrap_or("-");
120+
let ip = ips.first().map(|s| s.as_str()).unwrap_or("-");
121121
format!("IP: {}", ip)
122122
}),
123123
)

web/src/Setup.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import Spinner from "@cloudscape-design/components/spinner";
2727
import Wizard from "@cloudscape-design/components/wizard";
2828

2929
import { LabgridService, LabgridConfig } from "./SettingsLabgrid";
30+
import { MqttToggle } from "./MqttComponents";
3031
import { ConfigEditor } from "./ConfigEditor";
3132
import { useMqttState } from "./mqtt";
3233

@@ -130,6 +131,38 @@ export default function Setup() {
130131
</Container>
131132
),
132133
},
134+
{
135+
title: "Configure Software Updates",
136+
description:
137+
"Choose when and how to check for new software releases",
138+
content: (
139+
<Container>
140+
<Box variant="p">
141+
The LXA TAC uses <Link href="https://rauc.io/">RAUC</Link>{" "}
142+
to manage and install software updates. A RAUC software
143+
update updates all of the installed software components at
144+
once and falls back to the previous version if something
145+
went wrong.
146+
</Box>
147+
<Box variant="p">
148+
We continually improve the software experience on the TAC
149+
and ship new features, so make sure to stay up to date
150+
with new releases.
151+
</Box>
152+
<Box variant="p">
153+
Would you like the TAC to periodically connect to the
154+
update server and check for software updates? You will be
155+
notified about updates via the LXA TACs display and the
156+
web interface and can start the update process from there.
157+
</Box>
158+
<Box variant="p" padding="s">
159+
<MqttToggle topic="/v1/tac/update/enable_polling">
160+
Periodically check for updates
161+
</MqttToggle>
162+
</Box>
163+
</Container>
164+
),
165+
},
133166
{
134167
title: "Add SSH keys",
135168
description:

web/src/TacComponents.tsx

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import SpaceBetween from "@cloudscape-design/components/space-between";
3030
import Spinner from "@cloudscape-design/components/spinner";
3131
import Table from "@cloudscape-design/components/table";
3232

33-
import { MqttButton } from "./MqttComponents";
33+
import { MqttButton, MqttToggle } from "./MqttComponents";
3434
import { useMqttSubscription } from "./mqtt";
3535

3636
type RootfsSlot = {
@@ -221,12 +221,41 @@ export function SlotStatus() {
221221
}
222222
}
223223

224+
export function UpdateConfig() {
225+
return (
226+
<Container
227+
header={
228+
<Header
229+
variant="h3"
230+
description="Decide how updates are handled on this TAC"
231+
>
232+
Update Configuration
233+
</Header>
234+
}
235+
>
236+
<ColumnLayout columns={3} variant="text-grid">
237+
<Box>
238+
<Box variant="awsui-key-label">Update Polling</Box>
239+
<MqttToggle topic="/v1/tac/update/enable_polling">
240+
Periodically check for updates
241+
</MqttToggle>
242+
</Box>
243+
</ColumnLayout>
244+
</Container>
245+
);
246+
}
247+
224248
export function UpdateChannels() {
225249
const channels_topic = useMqttSubscription<Array<Channel>>(
226250
"/v1/tac/update/channels",
227251
);
252+
const enable_polling_topic = useMqttSubscription<Array<Channel>>(
253+
"/v1/tac/update/enable_polling",
254+
);
228255

229256
const channels = channels_topic !== undefined ? channels_topic : [];
257+
const enable_polling =
258+
enable_polling_topic !== undefined ? enable_polling_topic : false;
230259

231260
return (
232261
<Table
@@ -260,7 +289,9 @@ export function UpdateChannels() {
260289
{
261290
id: "enabled",
262291
header: "Enabled",
263-
cell: (e) => <Checkbox checked={e.enabled} />,
292+
cell: (e) => (
293+
<Checkbox checked={e.enabled} disabled={!enable_polling} />
294+
),
264295
},
265296
{
266297
id: "description",
@@ -276,8 +307,12 @@ export function UpdateChannels() {
276307
},
277308
{
278309
id: "interval",
279-
header: "Update Interval",
310+
header: "Polling Interval",
280311
cell: (e) => {
312+
if (!enable_polling) {
313+
return "Disabled";
314+
}
315+
281316
if (!e.polling_interval) {
282317
return "Never";
283318
}
@@ -313,7 +348,11 @@ export function UpdateChannels() {
313348
}
314349

315350
if (!e.bundle) {
316-
return <Spinner />;
351+
if (enable_polling) {
352+
return <Spinner />;
353+
} else {
354+
return "Polling disabled";
355+
}
317356
}
318357

319358
if (!e.bundle.newer_than_installed) {
@@ -435,6 +474,7 @@ export function UpdateContainer() {
435474
}
436475
>
437476
<SpaceBetween size="m">
477+
<UpdateConfig />
438478
<UpdateChannels />
439479
<SlotStatus />
440480
</SpaceBetween>

0 commit comments

Comments
 (0)