forked from lix-project/lix-installer
Initial work on substituter
This commit is contained in:
parent
8d04ca8d3d
commit
6512b8d2cc
281
Cargo.lock
generated
281
Cargo.lock
generated
|
@ -134,6 +134,12 @@ version = "0.22.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
|
@ -146,6 +152,15 @@ version = "2.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.16.0"
|
||||
|
@ -177,11 +192,22 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.96"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
|
@ -282,6 +308,12 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
|
@ -298,6 +330,52 @@ version = "0.8.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "4.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"curve25519-dalek-derive",
|
||||
"digest",
|
||||
"fiat-crypto",
|
||||
"rustc_version",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek-derive"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.8"
|
||||
|
@ -333,6 +411,16 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.3.11"
|
||||
|
@ -343,6 +431,16 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "5.0.1"
|
||||
|
@ -391,6 +489,31 @@ version = "1.0.17"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
|
||||
|
||||
[[package]]
|
||||
name = "ed25519"
|
||||
version = "2.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
|
||||
dependencies = [
|
||||
"pkcs8",
|
||||
"serde",
|
||||
"signature",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519-dalek"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"ed25519",
|
||||
"serde",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.11.0"
|
||||
|
@ -459,6 +582,12 @@ version = "2.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
|
||||
|
||||
[[package]]
|
||||
name = "fiat-crypto"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.23"
|
||||
|
@ -553,6 +682,16 @@ dependencies = [
|
|||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.14"
|
||||
|
@ -817,6 +956,15 @@ version = "1.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
|
||||
|
||||
[[package]]
|
||||
name = "is_executable"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa9acdc6d67b75e626ad644734e8bc6df893d9cd2a834129065d3dd6158ea9c8"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.0"
|
||||
|
@ -829,6 +977,15 @@ version = "1.0.11"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.69"
|
||||
|
@ -877,17 +1034,20 @@ name = "lix-installer"
|
|||
version = "0.17.1"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"bytes 1.6.0",
|
||||
"clap",
|
||||
"color-eyre",
|
||||
"dirs",
|
||||
"dyn-clone",
|
||||
"ed25519-dalek",
|
||||
"eyre",
|
||||
"glob",
|
||||
"indexmap 2.2.6",
|
||||
"is_ci",
|
||||
"nix",
|
||||
"nix-config-parser",
|
||||
"nix-nar",
|
||||
"os-release",
|
||||
"owo-colors 4.0.0",
|
||||
"plist",
|
||||
|
@ -897,6 +1057,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"sha2",
|
||||
"strum",
|
||||
"sysctl",
|
||||
"tar",
|
||||
|
@ -914,6 +1075,7 @@ dependencies = [
|
|||
"walkdir",
|
||||
"which",
|
||||
"xz2",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1007,6 +1169,18 @@ dependencies = [
|
|||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix-nar"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5549158a8b179c4fcd06a19f4bcc557db60c9cbd6771add9563f46c8d0325b5"
|
||||
dependencies = [
|
||||
"camino",
|
||||
"is_executable",
|
||||
"symlink",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
|
@ -1140,6 +1314,16 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
|
||||
dependencies = [
|
||||
"der",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.30"
|
||||
|
@ -1367,6 +1551,15 @@ version = "0.1.23"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.34"
|
||||
|
@ -1574,6 +1767,17 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
|
@ -1592,6 +1796,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
|
@ -1623,6 +1836,16 @@ version = "0.9.8"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
|
@ -1657,6 +1880,12 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "supports-color"
|
||||
version = "2.1.0"
|
||||
|
@ -1667,6 +1896,12 @@ dependencies = [
|
|||
"is_ci",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "symlink"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.60"
|
||||
|
@ -1995,6 +2230,12 @@ version = "0.2.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "typetag"
|
||||
version = "0.2.16"
|
||||
|
@ -2079,6 +2320,12 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
|
@ -2421,3 +2668,37 @@ dependencies = [
|
|||
"lzma-sys",
|
||||
"tokio-io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9"
|
||||
dependencies = [
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "7.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa556e971e7b568dc775c136fc9de8c779b1c2fc3a63defaafadffdbd3181afa"
|
||||
dependencies = [
|
||||
"zstd-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "2.0.12+zstd.1.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
|
|
@ -60,6 +60,11 @@ which = "6.0.0"
|
|||
sysctl = "0.5.4"
|
||||
walkdir = "2.3.3"
|
||||
indexmap = { version = "2.0.2", features = ["serde"] }
|
||||
nix-nar = "0.3.0"
|
||||
zstd = { version = "0.13.2", default-features = false }
|
||||
sha2 = "0.10.8"
|
||||
ed25519-dalek = { version = "2.1.1", features = ["serde"] }
|
||||
base64 = "0.22.1"
|
||||
|
||||
[dev-dependencies]
|
||||
eyre = { version = "0.6.8", default-features = false, features = [ "track-caller" ] }
|
||||
|
|
360
src/action/base/fetch_and_unpack_nix_substituter.rs
Normal file
360
src/action/base/fetch_and_unpack_nix_substituter.rs
Normal file
|
@ -0,0 +1,360 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use base64::Engine;
|
||||
use ed25519_dalek::VerifyingKey;
|
||||
use reqwest::Url;
|
||||
use tracing::{span, Span};
|
||||
|
||||
use crate::{
|
||||
action::{Action, ActionDescription, ActionError, ActionErrorKind, ActionTag, StatefulAction},
|
||||
parse_ssl_cert,
|
||||
};
|
||||
|
||||
/// Fetch an output and its dependencies from a set of substituters,
|
||||
/// given an output path, subsititer URLs, and trusted keys.
|
||||
/// Also generates a ".reginfo" compatible with `nix-store --load-db`
|
||||
/// Only implements a subset of nix substitution features:
|
||||
/// * Substituter priorites are highest to lowest as given to [`plan`],
|
||||
/// instead of priority from nix-cache-info
|
||||
/// * narinfo signatures are always required
|
||||
/// * ca-derivations are not supported
|
||||
/// * NarHash must be sha256
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct FetchAndUnpackNixSubstituter {
|
||||
/// Map from key name (e.g. cache.nixos.org-1) to parsed ed25519 key
|
||||
trusted_keys: HashMap<String, VerifyingKey>,
|
||||
/// Base URLs for substituters, e.g. https://cache.nixos.org/
|
||||
substituters: Vec<Url>,
|
||||
/// Desired derivation output, e.g.
|
||||
/// `/nix/store/n50jk09x9hshwx1lh6k3qaiygc7yxbv9-lix-2.90.0-rc1`
|
||||
target: StorePath,
|
||||
/// Destination directory, normally temporary.
|
||||
/// For compatibility with tarballs, files will be placed in
|
||||
/// the nix/store subdirectory of the destination
|
||||
dest: PathBuf,
|
||||
/// Proxy used for all requests from substituters
|
||||
proxy: Option<Url>,
|
||||
/// Extra SSL certificates trusted for all requests
|
||||
ssl_cert_file: Option<PathBuf>,
|
||||
}
|
||||
|
||||
/// Root directory of the nix store.
|
||||
/// Technically this could be something other than /nix/store,
|
||||
/// but that is rarely done in production
|
||||
const STORE_DIR: &str = "/nix/store/";
|
||||
|
||||
impl FetchAndUnpackNixSubstituter {
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
pub async fn plan(
|
||||
target: PathBuf,
|
||||
dest: PathBuf,
|
||||
trusted_keys: Vec<String>,
|
||||
substituters: Vec<Url>,
|
||||
proxy: Option<Url>,
|
||||
ssl_cert_file: Option<PathBuf>,
|
||||
) -> Result<StatefulAction<Self>, ActionError> {
|
||||
let trusted_keys_parsed = trusted_keys
|
||||
.iter()
|
||||
.map(|key| parse_key(key))
|
||||
.collect::<Result<HashMap<_, _>, _>>()
|
||||
.map_err(Self::error)?;
|
||||
|
||||
if let Some(proxy) = &proxy {
|
||||
match proxy.scheme() {
|
||||
"https" | "http" | "socks5" => (),
|
||||
_ => return Err(Self::error(SubstitutionError::UnknownProxyScheme)),
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(ssl_cert_file) = &ssl_cert_file {
|
||||
parse_ssl_cert(ssl_cert_file).await.map_err(Self::error)?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
target: StorePath::from_full_path(target)
|
||||
.ok_or_else(|| Self::error(SubstitutionError::InvalidStorePath))?,
|
||||
trusted_keys: trusted_keys_parsed,
|
||||
dest,
|
||||
proxy,
|
||||
substituters,
|
||||
ssl_cert_file,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
#[typetag::serde(name = "fetch_and_unpack_nix")]
|
||||
impl Action for FetchAndUnpackNixSubstituter {
|
||||
fn action_tag() -> ActionTag {
|
||||
ActionTag("fetch_and_unpack_nix_substituter")
|
||||
}
|
||||
fn tracing_synopsis(&self) -> String {
|
||||
format!(
|
||||
"Fetch `{}` from substituters to `{}`",
|
||||
self.target.full_path.to_string_lossy(),
|
||||
self.dest.display()
|
||||
)
|
||||
}
|
||||
|
||||
fn tracing_span(&self) -> Span {
|
||||
let span = span!(
|
||||
tracing::Level::DEBUG,
|
||||
"fetch_and_unpack_nix_substituter",
|
||||
target = tracing::field::debug(&self.target.full_path),
|
||||
proxy = tracing::field::Empty,
|
||||
ssl_cert_file = tracing::field::Empty,
|
||||
dest = tracing::field::display(self.dest.display()),
|
||||
);
|
||||
if let Some(proxy) = &self.proxy {
|
||||
span.record("proxy", tracing::field::display(&proxy));
|
||||
}
|
||||
if let Some(ssl_cert_file) = &self.ssl_cert_file {
|
||||
span.record(
|
||||
"ssl_cert_file",
|
||||
tracing::field::display(&ssl_cert_file.display()),
|
||||
);
|
||||
}
|
||||
span
|
||||
}
|
||||
|
||||
fn execute_description(&self) -> Vec<ActionDescription> {
|
||||
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn execute(&mut self) -> Result<(), ActionError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn revert_description(&self) -> Vec<ActionDescription> {
|
||||
vec![/* Deliberately empty -- this is a noop */]
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a nix trusted key into name and ed25519
|
||||
fn parse_key(key: &str) -> Result<(String, VerifyingKey), SubstitutionError> {
|
||||
let (name, key_base64) = key
|
||||
.split_once(':')
|
||||
.ok_or_else(|| SubstitutionError::PublicKey)?;
|
||||
|
||||
// seems to be the best way to handle keys both with and without padding
|
||||
let key_bytes = base64::engine::general_purpose::STANDARD_NO_PAD
|
||||
.decode(key_base64.trim_end_matches("=").as_bytes())
|
||||
.map_err(|_| SubstitutionError::PublicKey)?;
|
||||
|
||||
let key_array: [u8; ed25519_dalek::PUBLIC_KEY_LENGTH] = key_bytes
|
||||
.try_into()
|
||||
.map_err(|_| SubstitutionError::PublicKey)?;
|
||||
|
||||
let verifying_key = ed25519_dalek::VerifyingKey::from_bytes(&key_array)
|
||||
.map_err(|_| SubstitutionError::PublicKey)?;
|
||||
|
||||
Ok((name.to_string(), verifying_key))
|
||||
}
|
||||
|
||||
/// Utility struct representing a store path
|
||||
#[derive(Clone, Eq, Debug)]
|
||||
struct StorePath {
|
||||
/// Full path of the output including STORE_DIR,
|
||||
/// as seen in StorePath in narinfo
|
||||
/// e.g. `/nix/store/n50jk09x9hshwx1lh6k3qaiygc7yxbv9-lix-2.90.0-rc1`
|
||||
pub full_path: String,
|
||||
/// The base32 hash part of a store path,
|
||||
/// e.g. `n50jk09x9hshwx1lh6k3qaiygc7yxbv9`
|
||||
pub digest: String,
|
||||
/// The full name of a path, not including STORE_DIR,
|
||||
/// e.g. `n50jk09x9hshwx1lh6k3qaiygc7yxbv9-lix-2.90.0-rc1`
|
||||
pub full_name: String,
|
||||
}
|
||||
|
||||
impl StorePath {
|
||||
pub fn from_full_path(full_path: &str) -> Option<Self> {
|
||||
if !full_path.starts_with(STORE_DIR) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let remaining =
|
||||
|
||||
let full_name = full_path.file_name()?.to_str()?.to_owned();
|
||||
|
||||
let (digest, _) = full_name.split_once('-')?;
|
||||
Some(Self {
|
||||
full_path,
|
||||
digest: digest.to_owned(),
|
||||
full_name,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_full_name(full_name: String) -> Option<Self> {
|
||||
let (digest, _) = full_name.split_once('-')?;
|
||||
|
||||
Some(Self {
|
||||
full_path: Path::new(STORE_DIR).join(&full_name),
|
||||
digest: digest.to_string(),
|
||||
full_name,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for StorePath {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.full_path == other.full_path
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for StorePath {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
self.full_path.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for StorePath {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_string(StorePathVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct StorePathVisitor;
|
||||
impl serde::de::Visitor<'_> for StorePathVisitor {
|
||||
type Value = StorePath;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("a valid nix store path starting with /nix/store")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
StorePath::from_full_path(Path::new(value).to_owned())
|
||||
.ok_or_else(|| E::custom("invalid store path"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Compression types for nar files.
|
||||
/// Not exhaustive, this is just what
|
||||
/// I've seen in the real world
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
enum NarCompression {
|
||||
Zstd,
|
||||
Xz,
|
||||
}
|
||||
|
||||
impl NarCompression {
|
||||
pub fn from_name(name: &str) -> Option<Self> {
|
||||
match name {
|
||||
"zstd" => Some(Self::Zstd),
|
||||
"xz" => Some(Self::Zstd),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracted data from a narinfo.
|
||||
/// May only be constructed by verifying signature
|
||||
/// Missing some fields, like Deriver and FileHash because they aren't signed
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct NarInfo {
|
||||
/// Store path represented by the nar
|
||||
pub store_path: StorePath,
|
||||
/// Full URL to download the nar
|
||||
pub url: Url,
|
||||
/// Method used to compress the nar
|
||||
pub compression: NarCompression,
|
||||
/// sha256 hash of the nar after decompression
|
||||
/// encoded in nix base32 format
|
||||
pub nar_hash: String,
|
||||
/// Size of the decompressed nar in bytes
|
||||
pub nar_size: u64,
|
||||
/// Other store paths referenced by
|
||||
pub references: Vec<StorePath>,
|
||||
}
|
||||
|
||||
impl NarInfo {
|
||||
fn parse(substituter_url: &Url, contents: &bytes::Bytes) -> Result<Self, SubstitutionError> {
|
||||
Ok(Self {
|
||||
store_path: todo!(),
|
||||
url: todo!(),
|
||||
compression: todo!(),
|
||||
nar_hash: todo!(),
|
||||
nar_size: todo!(),
|
||||
references: todo!(),
|
||||
})
|
||||
}
|
||||
|
||||
fn verify(
|
||||
&self,
|
||||
trusted_keys: &HashMap<String, VerifyingKey>,
|
||||
) -> Result<(), SubstitutionError> {
|
||||
// Fingerprint format not documented, but implemented in lix:
|
||||
// https://git.lix.systems/lix-project/lix/src/commit/d461cc1d7b2f489c3886f147166ba5b5e0e37541/src/libstore/path-info.cc#L25
|
||||
let fingerprint = format!(
|
||||
"1;{};{};{};{}",
|
||||
self.store_path.full_path.to_string_lossy(),
|
||||
self.nar_hash,
|
||||
self.nar_size,
|
||||
self.references
|
||||
.iter()
|
||||
.map(|reference| reference.full_path)
|
||||
.collect::<Vec<String>>()
|
||||
.join(",")
|
||||
);
|
||||
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn parse_and_verify(
|
||||
trusted_keys: &HashMap<String, VerifyingKey>,
|
||||
substituter_url: &Url,
|
||||
expected_store_path: &StorePath,
|
||||
contents: &bytes::Bytes,
|
||||
) -> Result<Self, SubstitutionError> {
|
||||
let parsed = Self::parse(substituter_url, contents)?;
|
||||
|
||||
if &parsed.store_path != expected_store_path {
|
||||
return Err(SubstitutionError::BadNarInfo);
|
||||
}
|
||||
|
||||
parsed.verify(trusted_keys)?;
|
||||
|
||||
Ok(parsed)
|
||||
}
|
||||
}
|
||||
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum SubstitutionError {
|
||||
#[error("Unarchiving error")]
|
||||
Unarchive(#[source] std::io::Error),
|
||||
#[error("Unknown proxy scheme, `https://`, `socks5://`, and `http://` supported")]
|
||||
UnknownProxyScheme,
|
||||
#[error("Invalid public key")]
|
||||
/// Normally an ed25519_dalek::SignatureError,
|
||||
/// but that comes with no extra information so no need to include it
|
||||
PublicKey,
|
||||
#[error("Bad narinfo signature")]
|
||||
BadNarInfo,
|
||||
#[error("Invalid nix store path")]
|
||||
InvalidStorePath,
|
||||
}
|
||||
|
||||
impl From<SubstitutionError> for ActionErrorKind {
|
||||
fn from(val: SubstitutionError) -> Self {
|
||||
ActionErrorKind::Custom(Box::new(val))
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ pub(crate) mod create_or_merge_nix_config;
|
|||
pub(crate) mod create_user;
|
||||
pub(crate) mod delete_user;
|
||||
pub(crate) mod fetch_and_unpack_nix;
|
||||
pub(crate) mod fetch_and_unpack_nix_substituter;
|
||||
pub(crate) mod move_unpacked_nix;
|
||||
pub(crate) mod remove_directory;
|
||||
pub(crate) mod setup_default_profile;
|
||||
|
@ -22,6 +23,7 @@ pub use create_or_merge_nix_config::CreateOrMergeNixConfig;
|
|||
pub use create_user::CreateUser;
|
||||
pub use delete_user::DeleteUser;
|
||||
pub use fetch_and_unpack_nix::{FetchAndUnpackNix, FetchUrlError};
|
||||
pub use fetch_and_unpack_nix_substituter::{FetchAndUnpackNixSubstituter, SubstitutionError};
|
||||
pub use move_unpacked_nix::{MoveUnpackedNix, MoveUnpackedNixError};
|
||||
pub use remove_directory::RemoveDirectory;
|
||||
pub use setup_default_profile::{SetupDefaultProfile, SetupDefaultProfileError};
|
||||
|
|
Loading…
Reference in a new issue