git.delta.rocks / fleet / refs/commits / 615754ca0747

difftreelog

feat usbd

qoyyyxlkYaroslav Bolyukin2026-07-07parent: #51d5ea5.patch.diff

24 files changed

modifiedCargo.lockdiffbeforeafterboth
1912version = "0.2.0"1912version = "0.2.0"
1913dependencies = [1913dependencies = [
1914 "anyhow",1914 "anyhow",
1915 "base64 0.22.1",
1915 "camino",1916 "camino",
1916 "clap",1917 "clap",
1917 "clap_complete",1918 "clap_complete",
1918 "fleet-base",1919 "fleet-base",
1920 "fleet-usb",
1919 "futures",1921 "futures",
1920 "goodlog-subscriber",1922 "goodlog-subscriber",
1921 "itertools 0.15.0",1923 "itertools 0.15.0",
1924 "nix",
1922 "nix-eval",1925 "nix-eval",
1926 "rand 0.10.1",
1923 "remowt-fleet",1927 "remowt-fleet",
1924 "serde",1928 "serde",
1925 "serde_json",1929 "serde_json",
1928 "tempfile",1932 "tempfile",
1929 "tokio",1933 "tokio",
1930 "tracing",1934 "tracing",
1935 "zbus",
1936 "zstd",
1931]1937]
19321938
1933[[package]]1939[[package]]
2005 "unicode_categories",2011 "unicode_categories",
2006]2012]
2013
2014[[package]]
2015name = "fleet-usb"
2016version = "0.1.9"
2017dependencies = [
2018 "age",
2019 "anyhow",
2020 "base64 0.22.1",
2021 "camino",
2022 "chacha20poly1305",
2023 "hex",
2024 "hmac 0.13.0",
2025 "serde",
2026 "serde_json",
2027 "sha2 0.11.0",
2028]
2029
2030[[package]]
2031name = "fleet-usbd"
2032version = "0.1.9"
2033dependencies = [
2034 "age",
2035 "anyhow",
2036 "base64 0.22.1",
2037 "bytes",
2038 "camino",
2039 "clap",
2040 "fleet-usb",
2041 "futures",
2042 "goodlog-subscriber",
2043 "hex",
2044 "hostname",
2045 "http-body-util",
2046 "hyper",
2047 "hyper-util",
2048 "nix",
2049 "nix-eval",
2050 "rand 0.10.1",
2051 "tokio",
2052 "tracing",
2053]
20072054
2008[[package]]2055[[package]]
2009name = "fluent"2056name = "fluent"
7840source = "registry+https://github.com/rust-lang/crates.io-index"7887source = "registry+https://github.com/rust-lang/crates.io-index"
7841checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"7888checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
7889
7890[[package]]
7891name = "zstd"
7892version = "0.13.3"
7893source = "registry+https://github.com/rust-lang/crates.io-index"
7894checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
7895dependencies = [
7896 "zstd-safe",
7897]
7898
7899[[package]]
7900name = "zstd-safe"
7901version = "7.2.4"
7902source = "registry+https://github.com/rust-lang/crates.io-index"
7903checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
7904dependencies = [
7905 "zstd-sys",
7906]
7907
7908[[package]]
7909name = "zstd-sys"
7910version = "2.0.16+zstd.1.5.7"
7911source = "registry+https://github.com/rust-lang/crates.io-index"
7912checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
7913dependencies = [
7914 "cc",
7915 "pkg-config",
7916]
78427917
7843[[package]]7918[[package]]
7844name = "zvariant"7919name = "zvariant"
modifiedCargo.tomldiffbeforeafterboth
9[workspace.dependencies]9[workspace.dependencies]
10fleet-base = { path = "./crates/fleet-base" }10fleet-base = { path = "./crates/fleet-base" }
11fleet-shared = { path = "./crates/fleet-shared" }11fleet-shared = { path = "./crates/fleet-shared" }
12fleet-usb = { path = "./crates/fleet-usb" }
12nix-eval = { path = "./crates/nix-eval" }13nix-eval = { path = "./crates/nix-eval" }
13nixlike = { path = "./crates/nixlike" }14nixlike = { path = "./crates/nixlike" }
14opentelemetry-exporter-env = { path = "./crates/opentelemetry-exporter-env" }15opentelemetry-exporter-env = { path = "./crates/opentelemetry-exporter-env" }
36bindgen = "0.72.0"37bindgen = "0.72.0"
37bytes = "1.11.0"38bytes = "1.11.0"
38camino = "1.2.2"39camino = "1.2.2"
40chacha20poly1305 = "0.10"
39chrono = { version = "0.4.41", features = ["serde"] }41chrono = { version = "0.4.41", features = ["serde"] }
40clap = { version = "4.5", features = ["derive", "env", "unicode", "wrap_help"] }42clap = { version = "4.5", features = ["derive", "env", "unicode", "wrap_help"] }
41clap_complete = "4.5"43clap_complete = "4.5"
47hex = "0.4.3"49hex = "0.4.3"
48hmac = "0.13.0"50hmac = "0.13.0"
49hostname = "0.4.1"51hostname = "0.4.1"
52http-body-util = "0.1"
53hyper = { version = "1", features = ["http1", "server"] }
54hyper-util = { version = "0.1", features = ["tokio"] }
50human-repr = "1.1"55human-repr = "1.1"
51indicatif = "0.18"56indicatif = "0.18"
52indoc = "2.0.6"57indoc = "2.0.6"
85unicode_categories = "0.1.1"90unicode_categories = "0.1.1"
86vte = { version = "0.15.0", features = ["ansi"] }91vte = { version = "0.15.0", features = ["ansi"] }
87x25519-dalek = { version = "2.0.1", features = ["getrandom"] }92x25519-dalek = { version = "2.0.1", features = ["getrandom"] }
93zstd = "0.13"
88zbus = "5.16.0"94zbus = "5.16.0"
89zbus_polkit = "5.0.0"95zbus_polkit = "5.0.0"
90goodlog-subscriber = { version = "0.1.9", path = "crates/goodlog-subscriber" }96goodlog-subscriber = { version = "0.1.9", path = "crates/goodlog-subscriber" }
modifiedcmds/fleet/Cargo.tomldiffbeforeafterboth
99
10[dependencies]10[dependencies]
11anyhow.workspace = true11anyhow.workspace = true
12base64.workspace = true
12camino.workspace = true13camino.workspace = true
13clap.workspace = true14clap.workspace = true
14clap_complete.workspace = true15clap_complete.workspace = true
15fleet-base.workspace = true16fleet-base.workspace = true
17fleet-usb.workspace = true
18nix.workspace = true
16nix-eval.workspace = true19nix-eval.workspace = true
20rand.workspace = true
17remowt-fleet.workspace = true21remowt-fleet.workspace = true
18serde.workspace = true22serde.workspace = true
19serde_json.workspace = true23serde_json.workspace = true
20tempfile.workspace = true24tempfile.workspace = true
21tokio.workspace = true25tokio.workspace = true
22tracing.workspace = true26tracing.workspace = true
27zbus.workspace = true
28zstd.workspace = true
2329
24futures.workspace = true30futures.workspace = true
25itertools.workspace = true31itertools.workspace = true
modifiedcmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth
33 build_attr: String,33 build_attr: String,
34}34}
3535
36async fn build_task(config: Config, hostname: String, build_attr: &str) -> Result<Utf8PathBuf> {36pub(crate) async fn build_task(
37 config: Config,
38 hostname: String,
39 build_attr: &str,
modifiedcmds/fleet/src/cmds/mod.rsdiffbeforeafterboth
4pub mod rollback;4pub mod rollback;
5pub mod secrets;5pub mod secrets;
6pub mod tf;6pub mod tf;
7pub mod usbd;
78
addedcmds/fleet/src/cmds/usbd.rsdiffbeforeafterboth

no changes

modifiedcmds/fleet/src/main.rsdiffbeforeafterboth
14 rollback::RollbackSingle,14 rollback::RollbackSingle,
15 secrets::Secret,15 secrets::Secret,
16 tf::Tf,16 tf::Tf,
17 usbd::Usbd,
17};18};
18use fleet_base::{host::Config, opts::FleetOpts};19use fleet_base::{host::Config, opts::FleetOpts};
19use futures::{TryStreamExt, stream::FuturesUnordered};20use futures::{TryStreamExt, stream::FuturesUnordered};
74 Prefetch(Prefetch),75 Prefetch(Prefetch),
75 /// Config parsing76 /// Config parsing
76 Info(Info),77 Info(Info),
78 /// Airgapped updates over USB stick
79 #[clap(subcommand)]
80 Usbd(Usbd),
77 /// Command completions81 /// Command completions
78 #[clap(hide(true))]82 #[clap(hide(true))]
79 Complete(Complete),83 Complete(Complete),
100 Opts::RollbackSingle(r) => r.run(config, &opts).await?,104 Opts::RollbackSingle(r) => r.run(config, &opts).await?,
101 Opts::Secret(s) => s.run(config, &opts).await?,105 Opts::Secret(s) => s.run(config, &opts).await?,
102 Opts::Info(i) => i.run(config).await?,106 Opts::Info(i) => i.run(config).await?,
107 Opts::Usbd(u) => u.run(config).await?,
103 Opts::Prefetch(p) => p.run(config).await?,108 Opts::Prefetch(p) => p.run(config).await?,
104 Opts::Tf(t) => t.run(config).await?,109 Opts::Tf(t) => t.run(config).await?,
105 // TODO: actually parse commands before starting the async runtime110 // TODO: actually parse commands before starting the async runtime
addedcmds/usbd/Cargo.tomldiffbeforeafterboth

no changes

addedcmds/usbd/src/main.rsdiffbeforeafterboth

no changes

addedcmds/usbd/src/server.rsdiffbeforeafterboth

no changes

addedcrates/fleet-usb/Cargo.tomldiffbeforeafterboth

no changes

addedcrates/fleet-usb/src/lib.rsdiffbeforeafterboth

no changes

addedcrates/fleet-usb/src/manifest.rsdiffbeforeafterboth

no changes

addedcrates/fleet-usb/src/names.rsdiffbeforeafterboth

no changes

addedcrates/fleet-usb/src/stream.rsdiffbeforeafterboth

no changes

modifiedcrates/nix-eval/src/lib.ccdiffbeforeafterboth
14#include <nix/util/hash.hh>14#include <nix/util/hash.hh>
15#include <nix/util/posix-source-accessor.hh>15#include <nix/util/posix-source-accessor.hh>
16#include <nix/util/ref.hh>16#include <nix/util/ref.hh>
17#include <nix/util/serialise.hh>
17#include <nix/util/signature/local-keys.hh>18#include <nix/util/signature/local-keys.hh>
18#include <nix/util/signature/signer.hh>19#include <nix/util/signature/signer.hh>
19#include <nix/util/source-path.hh>20#include <nix/util/source-path.hh>
184 }185 }
185}186}
187
188CxxPathInfo query_path_info(Store *store, rust::Str path) {
189 CxxPathInfo out{rust::String(), rust::String(), 0, {}, {}};
190 try {
191 auto nixStore = store->ptr;
192 auto sp = nixStore->parseStorePath(std::string(path));
193 auto info = nixStore->queryPathInfo(sp);
194 out.nar_hash =
195 rust::String(info->narHash.to_string(nix::HashFormat::Nix32, true));
196 out.nar_size = info->narSize;
197 for (auto &r : info->references)
198 out.references.push_back(rust::String(nixStore->printStorePath(r)));
199 for (auto &s : info->sigs)
200 out.sigs.push_back(rust::String(s.to_string()));
201 } catch (const std::exception &e) {
202 out.error = rust::String(e.what());
203 }
204 return out;
205}
206
207CxxBuildResult compute_closure(Store *store, rust::Str path) {
208 CxxBuildResult res{rust::String(), {}};
209 try {
210 auto nixStore = store->ptr;
211 auto root = nixStore->parseStorePath(std::string(path));
212 nix::StorePathSet closure;
213 nixStore->computeFSClosure(root, closure);
214 for (auto &p : closure)
215 res.outputs.push_back(rust::String(nixStore->printStorePath(p)));
216 } catch (const std::exception &e) {
217 res.error = rust::String(e.what());
218 }
219 return res;
220}
221
222namespace {
223struct RustFnSink : nix::Sink {
224 size_t data;
225 rust::Fn<bool(size_t, rust::Slice<const uint8_t>)> sink;
226 RustFnSink(size_t data,
227 rust::Fn<bool(size_t, rust::Slice<const uint8_t>)> sink)
228 : data(data), sink(sink) {}
229 void operator()(std::string_view chunk) override {
230 bool ok = sink(data, rust::Slice<const uint8_t>(
231 reinterpret_cast<const uint8_t *>(chunk.data()),
232 chunk.size()));
233 if (!ok)
234 throw nix::Error("nar sink aborted");
235 }
236};
237} // namespace
238
239rust::String nar_from_path(Store *store, rust::Str path, size_t sink_data,
240 rust::Fn<bool(size_t, rust::Slice<const uint8_t>)> sink) {
241 try {
242 auto nixStore = store->ptr;
243 auto sp = nixStore->parseStorePath(std::string(path));
244 RustFnSink s(sink_data, sink);
245 nixStore->narFromPath(sp, s);
246 return rust::String();
247 } catch (const std::exception &e) {
248 return rust::String(e.what());
249 }
250}
186251
187AddFileToStoreResult add_file_to_store(Store *store, rust::Str name,252AddFileToStoreResult add_file_to_store(Store *store, rust::Str name,
188 rust::Str path) {253 rust::Str path) {
modifiedcrates/nix-eval/src/lib.hhdiffbeforeafterboth
3131
32bool is_valid_path(Store *store, rust::Str path);32bool is_valid_path(Store *store, rust::Str path);
33
34struct CxxPathInfo;
35CxxPathInfo query_path_info(Store *store, rust::Str path);
36
37CxxBuildResult compute_closure(Store *store, rust::Str path);
38
39rust::String nar_from_path(Store *store, rust::Str path, size_t sink_data,
40 rust::Fn<bool(size_t, rust::Slice<const uint8_t>)> sink);
3341
modifiedcrates/nix-eval/src/lib.rsdiffbeforeafterboth
90 error: String,90 error: String,
91 outputs: Vec<String>,91 outputs: Vec<String>,
92 }92 }
93 struct CxxPathInfo {
94 error: String,
95 nar_hash: String,
96 nar_size: u64,
97 references: Vec<String>,
98 sigs: Vec<String>,
99 }
93 unsafe extern "C++" {100 unsafe extern "C++" {
94 type nix_fetchers_settings;101 type nix_fetchers_settings;
95 type Store;102 type Store;
130 #[allow(clippy::missing_safety_doc)]137 #[allow(clippy::missing_safety_doc)]
131 unsafe fn is_valid_path(store: *mut Store, path: &str) -> bool;138 unsafe fn is_valid_path(store: *mut Store, path: &str) -> bool;
139
140 #[allow(clippy::missing_safety_doc)]
141 unsafe fn query_path_info(store: *mut Store, path: &str) -> CxxPathInfo;
142
143 #[allow(clippy::missing_safety_doc)]
144 unsafe fn compute_closure(store: *mut Store, path: &str) -> CxxBuildResult;
145
146 #[allow(clippy::missing_safety_doc)]
147 unsafe fn nar_from_path(
148 store: *mut Store,
149 path: &str,
150 sink_data: usize,
151 sink: fn(usize, &[u8]) -> bool,
152 ) -> String;
132 }153 }
133}154}
134155
535unsafe impl Send for Store {}556unsafe impl Send for Store {}
536unsafe impl Sync for Store {}557unsafe impl Sync for Store {}
558
559#[derive(Debug, Clone)]
560pub struct PathInfo {
561 pub nar_hash: String,
562 pub nar_size: u64,
563 pub references: Vec<Utf8PathBuf>,
564 pub sigs: Vec<String>,
565}
537566
538pub fn eval_store() -> Arc<Store> {567pub fn eval_store() -> Arc<Store> {
539 GLOBAL_STATE.store.clone()568 GLOBAL_STATE.store.clone()
622 unsafe { nix_cxx::is_valid_path(self.as_ptr().cast(), path.as_str()) }651 unsafe { nix_cxx::is_valid_path(self.as_ptr().cast(), path.as_str()) }
623 }652 }
653
654 #[instrument(skip(self))]
655 pub fn query_path_info(&self, path: &Utf8Path) -> Result<PathInfo> {
656 let res = unsafe { nix_cxx::query_path_info(self.as_ptr().cast(), path.as_str()) };
657 if !res.error.is_empty() {
658 bail!("failed to query path info for {path}: {}", res.error);
659 }
660 Ok(PathInfo {
661 nar_hash: res.nar_hash,
662 nar_size: res.nar_size,
663 references: res.references.into_iter().map(Utf8PathBuf::from).collect(),
664 sigs: res.sigs,
665 })
666 }
667
668 #[instrument(skip(self))]
669 pub fn compute_closure(&self, path: &Utf8Path) -> Result<Vec<Utf8PathBuf>> {
670 let res = unsafe { nix_cxx::compute_closure(self.as_ptr().cast(), path.as_str()) };
671 if !res.error.is_empty() {
672 bail!("failed to compute closure of {path}: {}", res.error);
673 }
674 Ok(res.outputs.into_iter().map(Utf8PathBuf::from).collect())
675 }
676
677 #[instrument(skip(self, out))]
678 pub fn nar_from_path(&self, path: &Utf8Path, out: &mut dyn std::io::Write) -> Result<()> {
679 struct SinkState<'a> {
680 out: &'a mut dyn std::io::Write,
681 error: Option<std::io::Error>,
682 }
683 fn sink(state: usize, data: &[u8]) -> bool {
684 let state = unsafe { &mut *(state as *mut SinkState) };
685 match state.out.write_all(data) {
686 Ok(()) => true,
687 Err(e) => {
688 state.error = Some(e);
689 false
690 }
691 }
692 }
693 let mut state = SinkState { out, error: None };
694 let msg = unsafe {
695 nix_cxx::nar_from_path(
696 self.as_ptr().cast(),
697 path.as_str(),
698 (&raw mut state) as usize,
699 sink,
700 )
701 };
702 if let Some(e) = state.error {
703 return Err(anyhow!(e).context(format!("nar sink failed for {path}")));
704 }
705 if !msg.is_empty() {
706 bail!("failed to dump nar of {path}: {msg}");
707 }
708 Ok(())
709 }
624710
625 #[instrument(skip(self))]711 #[instrument(skip(self))]
626 pub fn build_drv_outputs(712 pub fn build_drv_outputs(
addeddocs/features/usbd.adocdiffbeforeafterboth

no changes

modifiedlib/flakePart.nixdiffbeforeafterboth
72 })72 })
73 fleet-install-secrets73 fleet-install-secrets
74 fleet-generator-helper74 fleet-generator-helper
75 fleet-usbd
75 remowt-plugin-fleet76 remowt-plugin-fleet
76 ;77 ;
77 })78 })
modifiedmodules/nixos/module-list.nixdiffbeforeafterboth
3 ./secrets.nix3 ./secrets.nix
4 ./rollback.nix4 ./rollback.nix
5 ./nix-sign.nix5 ./nix-sign.nix
6 ./usbd.nix
6 ./online.nix7 ./online.nix
7 ./polkit.nix8 ./polkit.nix
8 ./top-level.nix9 ./top-level.nix
addedmodules/nixos/usbd.nixdiffbeforeafterboth

no changes

modifiedpkgs/default.nixdiffbeforeafterboth
10 fleet = callPackage ./fleet.nix { inherit craneLib inputs remowt-agents-bundle; };10 fleet = callPackage ./fleet.nix { inherit craneLib inputs remowt-agents-bundle; };
11 fleet-install-secrets = callPackage ./fleet-install-secrets.nix { inherit craneLib; };11 fleet-install-secrets = callPackage ./fleet-install-secrets.nix { inherit craneLib; };
12 fleet-generator-helper = callPackage ./fleet-generator-helper.nix { inherit craneLib; };12 fleet-generator-helper = callPackage ./fleet-generator-helper.nix { inherit craneLib; };
13 fleet-usbd = callPackage ./fleet-usbd.nix { inherit craneLib inputs; };
1314
14 inherit remowt-agents-bundle;15 inherit remowt-agents-bundle;
15 remowt-plugin-fleet = callPackage ./remowt-plugin-fleet.nix { inherit craneLib inputs; };16 remowt-plugin-fleet = callPackage ./remowt-plugin-fleet.nix { inherit craneLib inputs; };
addedpkgs/fleet-usbd.nixdiffbeforeafterboth

no changes