fiofleet 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fiofleet-0.5.0/LICENSE +17 -0
- fiofleet-0.5.0/PKG-INFO +211 -0
- fiofleet-0.5.0/README.md +190 -0
- fiofleet-0.5.0/pyproject.toml +35 -0
- fiofleet-0.5.0/setup.cfg +4 -0
- fiofleet-0.5.0/src/fiofleet/__init__.py +3 -0
- fiofleet-0.5.0/src/fiofleet/api.py +243 -0
- fiofleet-0.5.0/src/fiofleet/cli.py +474 -0
- fiofleet-0.5.0/src/fiofleet/config.py +123 -0
- fiofleet-0.5.0/src/fiofleet/ssh.py +35 -0
- fiofleet-0.5.0/src/fiofleet/transport.py +163 -0
- fiofleet-0.5.0/src/fiofleet/updates.py +236 -0
- fiofleet-0.5.0/src/fiofleet/wireguard.py +57 -0
- fiofleet-0.5.0/src/fiofleet.egg-info/PKG-INFO +211 -0
- fiofleet-0.5.0/src/fiofleet.egg-info/SOURCES.txt +23 -0
- fiofleet-0.5.0/src/fiofleet.egg-info/dependency_links.txt +1 -0
- fiofleet-0.5.0/src/fiofleet.egg-info/entry_points.txt +2 -0
- fiofleet-0.5.0/src/fiofleet.egg-info/requires.txt +7 -0
- fiofleet-0.5.0/src/fiofleet.egg-info/top_level.txt +1 -0
- fiofleet-0.5.0/tests/test_api.py +181 -0
- fiofleet-0.5.0/tests/test_cli.py +133 -0
- fiofleet-0.5.0/tests/test_config.py +36 -0
- fiofleet-0.5.0/tests/test_transport.py +125 -0
- fiofleet-0.5.0/tests/test_updates.py +206 -0
- fiofleet-0.5.0/tests/test_wireguard.py +51 -0
fiofleet-0.5.0/LICENSE
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
Copyright 2026 fiofleet contributors
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|
fiofleet-0.5.0/PKG-INFO
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fiofleet
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Bulk fleet operations for Foundries.io devices
|
|
5
|
+
Author: fiofleet contributors
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/gabbuman/fiofleet
|
|
8
|
+
Project-URL: Repository, https://github.com/gabbuman/fiofleet
|
|
9
|
+
Project-URL: Issues, https://github.com/gabbuman/fiofleet/issues
|
|
10
|
+
Keywords: foundries,fioctl,fleet,iot,wireguard,ota
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: click>=8.1
|
|
15
|
+
Requires-Dist: requests>=2.31
|
|
16
|
+
Requires-Dist: paramiko>=3
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
19
|
+
Requires-Dist: responses>=0.25; extra == "dev"
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# fiofleet
|
|
23
|
+
|
|
24
|
+
Bulk fleet operations for [Foundries.io](https://foundries.io) devices.
|
|
25
|
+
|
|
26
|
+
`fioctl` is great for single-device work. **fiofleet** is designed for when you have a large fleet of
|
|
27
|
+
devices, and want to enable/disable wireguard vpn and run ssh commands remotely en masse.
|
|
28
|
+
It's a thin, scriptable layer over the Foundries OTA API (and, optionally, `fioctl`).
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- **Device inventory** — list/show devices, filter by tag or group, with
|
|
33
|
+
online/offline detection.
|
|
34
|
+
- **OTA update reports** — for a tag/group, show each device's last update
|
|
35
|
+
broken down by stage (download → install), exactly *which stage failed* and
|
|
36
|
+
the error the device reported, plus a fleet-level pass/fail summary. Drill
|
|
37
|
+
into a single device's full update timeline with `ota stages`.
|
|
38
|
+
- **WireGuard fleet management** — enable/disable/status across many devices at
|
|
39
|
+
once, and *wait* until the platform confirms each device is a live VPN peer.
|
|
40
|
+
Works through the config API directly, so `fioctl` is **not required**.
|
|
41
|
+
- **Fan-out SSH/exec** — run a command (or open a shell) across a tag/group in
|
|
42
|
+
parallel, and collect the results as JSON (`--json`) so you can drive scripts
|
|
43
|
+
off them. Runs from *any* machine: fiofleet hops through your Factory
|
|
44
|
+
WireGuard server (a bastion) and SSHes to the devices from there, so the
|
|
45
|
+
operator doesn't need to be on the VPN.
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
pip install fiofleet
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Requires Python 3.9+. `fioctl` is optional — only needed if you pass
|
|
54
|
+
`--via-fioctl` to the WireGuard commands.
|
|
55
|
+
|
|
56
|
+
## Setup
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
fiofleet config set
|
|
60
|
+
# prompts for API token, factory name (and optionally an API base URL)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Or via env vars (these override the saved config):
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
export FOUNDRIES_API_TOKEN=...
|
|
67
|
+
export FOUNDRIES_FACTORY=my-factory
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Get your API token at https://app.foundries.io/settings/tokens/.
|
|
71
|
+
|
|
72
|
+
To use `ssh`/`exec` from a machine that isn't on the device VPN, point fiofleet
|
|
73
|
+
at your Factory WireGuard server (the bastion it hops through):
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
fiofleet config set-server --server vpn.example.com --server-user ops
|
|
77
|
+
# add --device-password ... if your devices use password (sshpass) auth
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Connecting to the server with an OpenSSH key** (e.g. an Azure VM running the
|
|
81
|
+
Factory WireGuard server — the same `.pem`/OpenSSH key you'd use for `ssh -i`):
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
fiofleet config set-server \
|
|
85
|
+
--server my-fio-vpn.eastus.cloudapp.azure.com \
|
|
86
|
+
--server-user azureuser \
|
|
87
|
+
--server-key ~/.ssh/azure-fio-vpn.pem \
|
|
88
|
+
--device-user fio
|
|
89
|
+
# device auth happens on the server: add --device-password ... if devices need
|
|
90
|
+
# sshpass, otherwise omit (the server's own key reaches the devices).
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`--server-key` is passed through to paramiko — anything `ssh -i` would accept
|
|
94
|
+
works (`.pem`, `~/.ssh/id_ed25519`, …). Omit it to fall back to your SSH agent
|
|
95
|
+
/ default keys, then password.
|
|
96
|
+
|
|
97
|
+
## Commands
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
# Factories your token can see
|
|
101
|
+
fiofleet factories
|
|
102
|
+
|
|
103
|
+
# Devices
|
|
104
|
+
fiofleet devices list
|
|
105
|
+
fiofleet devices list --tag prod-eu --online-only
|
|
106
|
+
fiofleet devices show my-device-01
|
|
107
|
+
|
|
108
|
+
# OTA update reports
|
|
109
|
+
fiofleet ota report --tag prod-eu # last update per device + fleet summary
|
|
110
|
+
fiofleet ota report --tag prod-eu --failed-only # just the devices that failed
|
|
111
|
+
fiofleet ota report --tag prod-eu --json # structured, for dashboards/CI
|
|
112
|
+
fiofleet ota report --tag prod-eu --target lmp-124 # every device that attempted target lmp-124
|
|
113
|
+
fiofleet ota report --tag prod-eu --target lmp-124 --failed-only # …and which of them failed
|
|
114
|
+
fiofleet ota stages my-device-01 # full stage timeline for one device
|
|
115
|
+
|
|
116
|
+
# WireGuard
|
|
117
|
+
fiofleet wg enable my-device-01
|
|
118
|
+
fiofleet wg enable --tag prod-eu --parallel 20 # enable + wait until applied
|
|
119
|
+
fiofleet wg status --tag prod-eu
|
|
120
|
+
fiofleet wg disable --tag prod-eu
|
|
121
|
+
fiofleet wg enable my-device-01 --via-fioctl # delegate to fioctl instead
|
|
122
|
+
|
|
123
|
+
# SSH / exec (hops through the configured WireGuard-server bastion by default)
|
|
124
|
+
fiofleet ssh my-device-01
|
|
125
|
+
fiofleet exec "uptime" --tag prod-eu
|
|
126
|
+
fiofleet exec "systemctl is-active aktualizr-lite" --tag prod-eu
|
|
127
|
+
fiofleet exec "fiotest" --tag prod-eu --json # collect results as JSON
|
|
128
|
+
fiofleet exec "reboot" --tag prod-eu --strict # non-zero exit if any device fails
|
|
129
|
+
fiofleet exec "uptime" --tag prod-eu --server vpn.example.com # ad-hoc bastion
|
|
130
|
+
fiofleet exec "uptime" --name dev-01 --direct # already on the VPN; skip the hop
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
A typical `ota report` looks like:
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
DEVICE RESULT FAILED@ TARGET WHEN
|
|
137
|
+
dev-us-01 FAILED install raspberrypi4-64-lmp-124 2026-05-21T08:14:02Z
|
|
138
|
+
-> install: Installation failed: ostree pull error: Server returned HTTP 500
|
|
139
|
+
dev-eu-02 IN_PROGRESS - raspberrypi4-64-lmp-124 2026-05-21T08:13:55Z
|
|
140
|
+
dev-eu-01 SUCCESS - raspberrypi4-64-lmp-124 2026-05-20T22:01:10Z
|
|
141
|
+
|
|
142
|
+
Fleet summary (3 device(s)):
|
|
143
|
+
FAILED 1 (install: 1)
|
|
144
|
+
IN_PROGRESS 1
|
|
145
|
+
SUCCESS 1
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## How OTA reporting works
|
|
149
|
+
|
|
150
|
+
Each device posts a stream of [libaktualizr](https://docs.foundries.io/latest/reference-manual/ota/ota.html)
|
|
151
|
+
report events to the device-gateway as it updates
|
|
152
|
+
(`EcuDownloadStarted`/`Completed`, `EcuInstallationStarted`/`Applied`/`Completed`).
|
|
153
|
+
fiofleet reads that stream from the OTA API's per-device `updates` view — the
|
|
154
|
+
same history `fioctl` shows — and collapses it into two operator-facing stages,
|
|
155
|
+
**download** and **install**. A stage that reports `success=false` marks the
|
|
156
|
+
update `FAILED` at that stage and surfaces the `details` the device attached;
|
|
157
|
+
an update that reached `EcuInstallationApplied` but not `…Completed` is
|
|
158
|
+
`IN_PROGRESS` (applied, awaiting the post-reboot confirmation). No agent on the
|
|
159
|
+
device is required — it's all read from the API.
|
|
160
|
+
|
|
161
|
+
Pass `--target X` to scope the report to one rollout: each device's update
|
|
162
|
+
history is searched (newest first) for an update whose target/version contains
|
|
163
|
+
`X`, and only devices that *actually attempted* it appear in the output —
|
|
164
|
+
their most recent attempt, with the same SUCCESS/FAILED/IN_PROGRESS verdict
|
|
165
|
+
and failing-stage detail.
|
|
166
|
+
|
|
167
|
+
## How WireGuard works here
|
|
168
|
+
|
|
169
|
+
Enabling WireGuard on a device writes a `wireguard-client` config entry (the same
|
|
170
|
+
one `fioctl devices config wireguard enable` writes). The platform assigns the
|
|
171
|
+
device a `10.42.42.x` address; the device applies the change on its next check-in.
|
|
172
|
+
|
|
173
|
+
`fiofleet wg status` / `--wait` poll the Foundries
|
|
174
|
+
[`wireguard-ips`](https://docs.foundries.io/latest/reference-manual/remote-access/wireguard.html)
|
|
175
|
+
view — the same one the [Factory WireGuard server](https://github.com/foundriesio/factory-wireguard-server)
|
|
176
|
+
reads to learn its peers — so "applied" means the platform actually considers the
|
|
177
|
+
device a live VPN peer, not just that a config was queued.
|
|
178
|
+
|
|
179
|
+
## How ssh/exec reach a device (the jump-host model)
|
|
180
|
+
|
|
181
|
+
A route to a device only exists on the Factory WireGuard server — it's peered
|
|
182
|
+
into the VPN and keeps `/etc/hosts` in sync with device VPN IPs. Rather than
|
|
183
|
+
require you to be on that box, fiofleet treats it as a **bastion**: it opens an
|
|
184
|
+
SSH connection to the server (via [paramiko](https://www.paramiko.org/)) and
|
|
185
|
+
runs the device `ssh` *there*. So:
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
your laptop ──SSH──► WireGuard server ──SSH──► device (10.42.42.x)
|
|
189
|
+
(anywhere) (on the VPN) (fio@…)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Device authentication therefore happens on the server — using the server's key,
|
|
193
|
+
or a password via `sshpass` (`--device-password`) — exactly as an admin SSHing
|
|
194
|
+
into the box by hand would. Configure the bastion once with
|
|
195
|
+
`fiofleet config set-server` (or pass `--server` ad hoc); pass `--direct` to
|
|
196
|
+
skip it when you're already on the VPN. fiofleet runs `ssh`; it doesn't manage
|
|
197
|
+
the tunnel itself.
|
|
198
|
+
|
|
199
|
+
## Development
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
pip install -e ".[dev]"
|
|
203
|
+
pytest
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
A local end-to-end harness (real Pi WireGuard server + containerised devices) lives
|
|
207
|
+
in [`harness/`](harness/README.md).
|
|
208
|
+
|
|
209
|
+
## License
|
|
210
|
+
|
|
211
|
+
Apache 2.0
|
fiofleet-0.5.0/README.md
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# fiofleet
|
|
2
|
+
|
|
3
|
+
Bulk fleet operations for [Foundries.io](https://foundries.io) devices.
|
|
4
|
+
|
|
5
|
+
`fioctl` is great for single-device work. **fiofleet** is designed for when you have a large fleet of
|
|
6
|
+
devices, and want to enable/disable wireguard vpn and run ssh commands remotely en masse.
|
|
7
|
+
It's a thin, scriptable layer over the Foundries OTA API (and, optionally, `fioctl`).
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Device inventory** — list/show devices, filter by tag or group, with
|
|
12
|
+
online/offline detection.
|
|
13
|
+
- **OTA update reports** — for a tag/group, show each device's last update
|
|
14
|
+
broken down by stage (download → install), exactly *which stage failed* and
|
|
15
|
+
the error the device reported, plus a fleet-level pass/fail summary. Drill
|
|
16
|
+
into a single device's full update timeline with `ota stages`.
|
|
17
|
+
- **WireGuard fleet management** — enable/disable/status across many devices at
|
|
18
|
+
once, and *wait* until the platform confirms each device is a live VPN peer.
|
|
19
|
+
Works through the config API directly, so `fioctl` is **not required**.
|
|
20
|
+
- **Fan-out SSH/exec** — run a command (or open a shell) across a tag/group in
|
|
21
|
+
parallel, and collect the results as JSON (`--json`) so you can drive scripts
|
|
22
|
+
off them. Runs from *any* machine: fiofleet hops through your Factory
|
|
23
|
+
WireGuard server (a bastion) and SSHes to the devices from there, so the
|
|
24
|
+
operator doesn't need to be on the VPN.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
pip install fiofleet
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Requires Python 3.9+. `fioctl` is optional — only needed if you pass
|
|
33
|
+
`--via-fioctl` to the WireGuard commands.
|
|
34
|
+
|
|
35
|
+
## Setup
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
fiofleet config set
|
|
39
|
+
# prompts for API token, factory name (and optionally an API base URL)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Or via env vars (these override the saved config):
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
export FOUNDRIES_API_TOKEN=...
|
|
46
|
+
export FOUNDRIES_FACTORY=my-factory
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Get your API token at https://app.foundries.io/settings/tokens/.
|
|
50
|
+
|
|
51
|
+
To use `ssh`/`exec` from a machine that isn't on the device VPN, point fiofleet
|
|
52
|
+
at your Factory WireGuard server (the bastion it hops through):
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
fiofleet config set-server --server vpn.example.com --server-user ops
|
|
56
|
+
# add --device-password ... if your devices use password (sshpass) auth
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Connecting to the server with an OpenSSH key** (e.g. an Azure VM running the
|
|
60
|
+
Factory WireGuard server — the same `.pem`/OpenSSH key you'd use for `ssh -i`):
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
fiofleet config set-server \
|
|
64
|
+
--server my-fio-vpn.eastus.cloudapp.azure.com \
|
|
65
|
+
--server-user azureuser \
|
|
66
|
+
--server-key ~/.ssh/azure-fio-vpn.pem \
|
|
67
|
+
--device-user fio
|
|
68
|
+
# device auth happens on the server: add --device-password ... if devices need
|
|
69
|
+
# sshpass, otherwise omit (the server's own key reaches the devices).
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`--server-key` is passed through to paramiko — anything `ssh -i` would accept
|
|
73
|
+
works (`.pem`, `~/.ssh/id_ed25519`, …). Omit it to fall back to your SSH agent
|
|
74
|
+
/ default keys, then password.
|
|
75
|
+
|
|
76
|
+
## Commands
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
# Factories your token can see
|
|
80
|
+
fiofleet factories
|
|
81
|
+
|
|
82
|
+
# Devices
|
|
83
|
+
fiofleet devices list
|
|
84
|
+
fiofleet devices list --tag prod-eu --online-only
|
|
85
|
+
fiofleet devices show my-device-01
|
|
86
|
+
|
|
87
|
+
# OTA update reports
|
|
88
|
+
fiofleet ota report --tag prod-eu # last update per device + fleet summary
|
|
89
|
+
fiofleet ota report --tag prod-eu --failed-only # just the devices that failed
|
|
90
|
+
fiofleet ota report --tag prod-eu --json # structured, for dashboards/CI
|
|
91
|
+
fiofleet ota report --tag prod-eu --target lmp-124 # every device that attempted target lmp-124
|
|
92
|
+
fiofleet ota report --tag prod-eu --target lmp-124 --failed-only # …and which of them failed
|
|
93
|
+
fiofleet ota stages my-device-01 # full stage timeline for one device
|
|
94
|
+
|
|
95
|
+
# WireGuard
|
|
96
|
+
fiofleet wg enable my-device-01
|
|
97
|
+
fiofleet wg enable --tag prod-eu --parallel 20 # enable + wait until applied
|
|
98
|
+
fiofleet wg status --tag prod-eu
|
|
99
|
+
fiofleet wg disable --tag prod-eu
|
|
100
|
+
fiofleet wg enable my-device-01 --via-fioctl # delegate to fioctl instead
|
|
101
|
+
|
|
102
|
+
# SSH / exec (hops through the configured WireGuard-server bastion by default)
|
|
103
|
+
fiofleet ssh my-device-01
|
|
104
|
+
fiofleet exec "uptime" --tag prod-eu
|
|
105
|
+
fiofleet exec "systemctl is-active aktualizr-lite" --tag prod-eu
|
|
106
|
+
fiofleet exec "fiotest" --tag prod-eu --json # collect results as JSON
|
|
107
|
+
fiofleet exec "reboot" --tag prod-eu --strict # non-zero exit if any device fails
|
|
108
|
+
fiofleet exec "uptime" --tag prod-eu --server vpn.example.com # ad-hoc bastion
|
|
109
|
+
fiofleet exec "uptime" --name dev-01 --direct # already on the VPN; skip the hop
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
A typical `ota report` looks like:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
DEVICE RESULT FAILED@ TARGET WHEN
|
|
116
|
+
dev-us-01 FAILED install raspberrypi4-64-lmp-124 2026-05-21T08:14:02Z
|
|
117
|
+
-> install: Installation failed: ostree pull error: Server returned HTTP 500
|
|
118
|
+
dev-eu-02 IN_PROGRESS - raspberrypi4-64-lmp-124 2026-05-21T08:13:55Z
|
|
119
|
+
dev-eu-01 SUCCESS - raspberrypi4-64-lmp-124 2026-05-20T22:01:10Z
|
|
120
|
+
|
|
121
|
+
Fleet summary (3 device(s)):
|
|
122
|
+
FAILED 1 (install: 1)
|
|
123
|
+
IN_PROGRESS 1
|
|
124
|
+
SUCCESS 1
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## How OTA reporting works
|
|
128
|
+
|
|
129
|
+
Each device posts a stream of [libaktualizr](https://docs.foundries.io/latest/reference-manual/ota/ota.html)
|
|
130
|
+
report events to the device-gateway as it updates
|
|
131
|
+
(`EcuDownloadStarted`/`Completed`, `EcuInstallationStarted`/`Applied`/`Completed`).
|
|
132
|
+
fiofleet reads that stream from the OTA API's per-device `updates` view — the
|
|
133
|
+
same history `fioctl` shows — and collapses it into two operator-facing stages,
|
|
134
|
+
**download** and **install**. A stage that reports `success=false` marks the
|
|
135
|
+
update `FAILED` at that stage and surfaces the `details` the device attached;
|
|
136
|
+
an update that reached `EcuInstallationApplied` but not `…Completed` is
|
|
137
|
+
`IN_PROGRESS` (applied, awaiting the post-reboot confirmation). No agent on the
|
|
138
|
+
device is required — it's all read from the API.
|
|
139
|
+
|
|
140
|
+
Pass `--target X` to scope the report to one rollout: each device's update
|
|
141
|
+
history is searched (newest first) for an update whose target/version contains
|
|
142
|
+
`X`, and only devices that *actually attempted* it appear in the output —
|
|
143
|
+
their most recent attempt, with the same SUCCESS/FAILED/IN_PROGRESS verdict
|
|
144
|
+
and failing-stage detail.
|
|
145
|
+
|
|
146
|
+
## How WireGuard works here
|
|
147
|
+
|
|
148
|
+
Enabling WireGuard on a device writes a `wireguard-client` config entry (the same
|
|
149
|
+
one `fioctl devices config wireguard enable` writes). The platform assigns the
|
|
150
|
+
device a `10.42.42.x` address; the device applies the change on its next check-in.
|
|
151
|
+
|
|
152
|
+
`fiofleet wg status` / `--wait` poll the Foundries
|
|
153
|
+
[`wireguard-ips`](https://docs.foundries.io/latest/reference-manual/remote-access/wireguard.html)
|
|
154
|
+
view — the same one the [Factory WireGuard server](https://github.com/foundriesio/factory-wireguard-server)
|
|
155
|
+
reads to learn its peers — so "applied" means the platform actually considers the
|
|
156
|
+
device a live VPN peer, not just that a config was queued.
|
|
157
|
+
|
|
158
|
+
## How ssh/exec reach a device (the jump-host model)
|
|
159
|
+
|
|
160
|
+
A route to a device only exists on the Factory WireGuard server — it's peered
|
|
161
|
+
into the VPN and keeps `/etc/hosts` in sync with device VPN IPs. Rather than
|
|
162
|
+
require you to be on that box, fiofleet treats it as a **bastion**: it opens an
|
|
163
|
+
SSH connection to the server (via [paramiko](https://www.paramiko.org/)) and
|
|
164
|
+
runs the device `ssh` *there*. So:
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
your laptop ──SSH──► WireGuard server ──SSH──► device (10.42.42.x)
|
|
168
|
+
(anywhere) (on the VPN) (fio@…)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Device authentication therefore happens on the server — using the server's key,
|
|
172
|
+
or a password via `sshpass` (`--device-password`) — exactly as an admin SSHing
|
|
173
|
+
into the box by hand would. Configure the bastion once with
|
|
174
|
+
`fiofleet config set-server` (or pass `--server` ad hoc); pass `--direct` to
|
|
175
|
+
skip it when you're already on the VPN. fiofleet runs `ssh`; it doesn't manage
|
|
176
|
+
the tunnel itself.
|
|
177
|
+
|
|
178
|
+
## Development
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
pip install -e ".[dev]"
|
|
182
|
+
pytest
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
A local end-to-end harness (real Pi WireGuard server + containerised devices) lives
|
|
186
|
+
in [`harness/`](harness/README.md).
|
|
187
|
+
|
|
188
|
+
## License
|
|
189
|
+
|
|
190
|
+
Apache 2.0
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "fiofleet"
|
|
7
|
+
version = "0.5.0"
|
|
8
|
+
description = "Bulk fleet operations for Foundries.io devices"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "Apache-2.0" }
|
|
12
|
+
authors = [{ name = "fiofleet contributors" }]
|
|
13
|
+
keywords = ["foundries", "fioctl", "fleet", "iot", "wireguard", "ota"]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"click>=8.1",
|
|
16
|
+
"requests>=2.31",
|
|
17
|
+
"paramiko>=3",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://github.com/gabbuman/fiofleet"
|
|
22
|
+
Repository = "https://github.com/gabbuman/fiofleet"
|
|
23
|
+
Issues = "https://github.com/gabbuman/fiofleet/issues"
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
dev = ["pytest>=7", "responses>=0.25"]
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
fiofleet = "fiofleet.cli:cli"
|
|
30
|
+
|
|
31
|
+
[tool.setuptools.packages.find]
|
|
32
|
+
where = ["src"]
|
|
33
|
+
|
|
34
|
+
[tool.pytest.ini_options]
|
|
35
|
+
testpaths = ["tests"]
|
fiofleet-0.5.0/setup.cfg
ADDED