Skip to main content

nix_installer/
settings.rs

1/*! Configurable knobs and their related errors
2*/
3use std::{collections::HashMap, fmt::Display, path::PathBuf, str::FromStr};
4
5#[cfg(feature = "cli")]
6use clap::{
7    error::{ContextKind, ContextValue},
8    ArgAction,
9};
10use url::Url;
11
12pub const SCRATCH_DIR: &str = "/nix/temp-install-dir";
13
14pub const NIX_TARBALL_PATH: &str = env!("NIX_INSTALLER_TARBALL_PATH");
15/// The NIX_INSTALLER_TARBALL_PATH environment variable should point to a target-appropriate
16/// Nix installation tarball, like nix-2.21.2-aarch64-darwin.tar.xz. The contents are embedded
17/// in the resulting binary.
18pub const NIX_TARBALL: &[u8] = include_bytes!(env!("NIX_INSTALLER_TARBALL_PATH"));
19
20#[cfg(feature = "determinate-nix")]
21/// The DETERMINATE_NIXD_BINARY_PATH environment variable should point to a target-appropriate
22/// static build of the Determinate Nixd binary. The contents are embedded in the resulting
23/// binary if the determinate-nix feature is turned on.
24pub const DETERMINATE_NIXD_BINARY: Option<&[u8]> =
25    Some(include_bytes!(env!("DETERMINATE_NIXD_BINARY_PATH")));
26
27#[cfg(not(feature = "determinate-nix"))]
28/// The DETERMINATE_NIXD_BINARY_PATH environment variable should point to a target-appropriate
29/// static build of the Determinate Nixd binary. The contents are embedded in the resulting
30/// binary if the determinate-nix feature is turned on.
31pub const DETERMINATE_NIXD_BINARY: Option<&[u8]> = None;
32
33#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, Copy, PartialEq, Eq)]
34#[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
35pub enum InitSystem {
36    None,
37    Systemd,
38    Launchd,
39}
40
41impl std::fmt::Display for InitSystem {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        match self {
44            InitSystem::None => write!(f, "none"),
45            InitSystem::Systemd => write!(f, "systemd"),
46            InitSystem::Launchd => write!(f, "launchd"),
47        }
48    }
49}
50
51/** Common settings used by all [`BuiltinPlanner`](crate::planner::BuiltinPlanner)s
52
53Settings which only apply to certain [`Planner`](crate::planner::Planner)s should be located in the planner.
54
55*/
56#[serde_with::serde_as]
57#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
58#[cfg_attr(feature = "cli", derive(clap::Parser))]
59pub struct CommonSettings {
60    /// Enable Determinate Nix. See: <https://determinate.systems/enterprise>
61    #[cfg_attr(
62        feature = "cli",
63        clap(
64            long = "determinate",
65            env = "NIX_INSTALLER_DETERMINATE",
66            default_value = "false"
67        )
68    )]
69    pub determinate_nix: bool,
70
71    /// Modify the user profile to automatically load Nix
72    #[cfg_attr(
73        feature = "cli",
74        clap(
75            action(ArgAction::SetFalse),
76            default_value = "true",
77            global = true,
78            env = "NIX_INSTALLER_MODIFY_PROFILE",
79            long = "no-modify-profile"
80        )
81    )]
82    pub modify_profile: bool,
83
84    /// The Nix build group name
85    #[cfg_attr(
86        feature = "cli",
87        clap(
88            long,
89            default_value = "nixbld",
90            env = "NIX_INSTALLER_NIX_BUILD_GROUP_NAME",
91            global = true
92        )
93    )]
94    pub nix_build_group_name: String,
95
96    /// The Nix build group GID
97    #[cfg_attr(
98        feature = "cli",
99        clap(long, env = "NIX_INSTALLER_NIX_BUILD_GROUP_ID", global = true)
100    )]
101    #[cfg_attr(
102        all(feature = "cli"),
103        clap(default_value_t = default_nix_build_group_id())
104    )]
105    pub nix_build_group_id: u32,
106
107    /// The Nix build user prefix (user numbers will be postfixed)
108    #[cfg_attr(
109        feature = "cli",
110        clap(long, env = "NIX_INSTALLER_NIX_BUILD_USER_PREFIX", global = true)
111    )]
112    #[cfg_attr(
113        all(target_os = "macos", feature = "cli"),
114        clap(default_value = "_nixbld")
115    )]
116    #[cfg_attr(
117        all(target_os = "linux", feature = "cli"),
118        clap(default_value = "nixbld")
119    )]
120    pub nix_build_user_prefix: String,
121
122    /// The number of build users to create
123    #[cfg_attr(
124        feature = "cli",
125        clap(
126            long,
127            alias = "daemon-user-count",
128            env = "NIX_INSTALLER_NIX_BUILD_USER_COUNT",
129            global = true
130        )
131    )]
132    #[cfg_attr(all(target_os = "macos", feature = "cli"), clap(default_value = "32"))]
133    #[cfg_attr(all(target_os = "linux", feature = "cli"), clap(default_value = "32"))]
134    pub nix_build_user_count: u32,
135
136    /// The Nix build user base UID (ascending)
137    #[cfg_attr(
138        feature = "cli",
139        clap(long, env = "NIX_INSTALLER_NIX_BUILD_USER_ID_BASE", global = true)
140    )]
141    #[cfg_attr(
142        all(target_os = "macos", feature = "cli"),
143        doc = "Service users on Mac should be between 200-400"
144    )]
145    #[cfg_attr(
146        all(feature = "cli"),
147        clap(default_value_t = default_nix_build_user_id_base())
148    )]
149    pub nix_build_user_id_base: u32,
150
151    /// The Nix package URL
152    #[cfg_attr(
153        feature = "cli",
154        clap(long, env = "NIX_INSTALLER_NIX_PACKAGE_URL", global = true, value_parser = clap::value_parser!(UrlOrPath), default_value = None)
155    )]
156    pub nix_package_url: Option<UrlOrPath>,
157
158    /// The proxy to use (if any); valid proxy bases are `https://$URL`, `http://$URL` and `socks5://$URL`
159    #[cfg_attr(feature = "cli", clap(long, env = "NIX_INSTALLER_PROXY"))]
160    pub proxy: Option<Url>,
161
162    /// An SSL cert to use (if any); used for fetching Nix and sets `ssl-cert-file` in `/etc/nix/nix.conf`
163    #[cfg_attr(feature = "cli", clap(long, env = "NIX_INSTALLER_SSL_CERT_FILE"))]
164    pub ssl_cert_file: Option<PathBuf>,
165
166    /// Extra configuration lines for `/etc/nix.conf`
167    #[cfg_attr(feature = "cli", clap(long, action = ArgAction::Append, num_args = 0.., env = "NIX_INSTALLER_EXTRA_CONF", global = true))]
168    pub extra_conf: Vec<UrlOrPathOrString>,
169
170    /// If `nix-installer` should forcibly recreate files it finds existing
171    #[cfg_attr(
172        feature = "cli",
173        clap(
174            long,
175            action(ArgAction::SetTrue),
176            default_value = "false",
177            global = true,
178            env = "NIX_INSTALLER_FORCE"
179        )
180    )]
181    pub force: bool,
182
183    #[cfg(feature = "diagnostics")]
184    /// Relate the install diagnostic to a specific value
185    #[cfg_attr(
186        feature = "cli",
187        clap(
188            long,
189            default_value = None,
190            env = "NIX_INSTALLER_DIAGNOSTIC_ATTRIBUTION",
191            global = true
192        )
193    )]
194    pub diagnostic_attribution: Option<String>,
195
196    #[cfg(feature = "diagnostics")]
197    /// The URL or file path for an installation diagnostic to be sent
198    ///
199    /// Sample of the data sent:
200    ///
201    /// {
202    ///     "attribution": null,
203    ///     "version": "0.4.0",
204    ///     "planner": "linux",
205    ///     "configured_settings": [ "modify_profile" ],
206    ///     "os_name": "Ubuntu",
207    ///     "os_version": "22.04.1 LTS (Jammy Jellyfish)",
208    ///     "triple": "x86_64-unknown-linux-gnu",
209    ///     "is_ci": false,
210    ///     "action": "Install",
211    ///     "status": "Success"
212    /// }
213    ///
214    /// To disable diagnostic reporting, unset the default with `--diagnostic-endpoint ""`, or `NIX_INSTALLER_DIAGNOSTIC_ENDPOINT=""`
215    #[clap(
216        long,
217        env = "NIX_INSTALLER_DIAGNOSTIC_ENDPOINT",
218        global = true,
219        value_parser = crate::diagnostics::diagnostic_endpoint_validator,
220        num_args = 0..=1, // Required to allow `--diagnostic-endpoint` or `NIX_INSTALLER_DIAGNOSTIC_ENDPOINT=""`
221        default_value = "https://install.determinate.systems/nix/diagnostic"
222    )]
223    pub diagnostic_endpoint: Option<String>,
224}
225
226fn default_nix_build_user_id_base() -> u32 {
227    use target_lexicon::OperatingSystem;
228
229    match OperatingSystem::host() {
230        OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin => 350,
231        _ => 30_000,
232    }
233}
234
235fn default_nix_build_group_id() -> u32 {
236    use target_lexicon::OperatingSystem;
237
238    match OperatingSystem::host() {
239        OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin => 350,
240        _ => 30_000,
241    }
242}
243
244impl CommonSettings {
245    /// The default settings for the given Architecture & Operating System
246    pub async fn default() -> Result<Self, InstallSettingsError> {
247        let nix_build_user_prefix;
248
249        use target_lexicon::{Architecture, OperatingSystem};
250        match (Architecture::host(), OperatingSystem::host()) {
251            (Architecture::X86_64, OperatingSystem::Linux) => {
252                nix_build_user_prefix = "nixbld";
253            },
254            (Architecture::X86_32(_), OperatingSystem::Linux) => {
255                nix_build_user_prefix = "nixbld";
256            },
257            (Architecture::Aarch64(_), OperatingSystem::Linux) => {
258                nix_build_user_prefix = "nixbld";
259            },
260            (Architecture::X86_64, OperatingSystem::MacOSX { .. })
261            | (Architecture::X86_64, OperatingSystem::Darwin) => {
262                nix_build_user_prefix = "_nixbld";
263            },
264            (Architecture::Aarch64(_), OperatingSystem::MacOSX { .. })
265            | (Architecture::Aarch64(_), OperatingSystem::Darwin) => {
266                nix_build_user_prefix = "_nixbld";
267            },
268            _ => {
269                return Err(InstallSettingsError::UnsupportedArchitecture(
270                    target_lexicon::HOST,
271                ))
272            },
273        };
274
275        Ok(Self {
276            determinate_nix: false,
277            modify_profile: true,
278            nix_build_group_name: String::from("nixbld"),
279            nix_build_group_id: default_nix_build_group_id(),
280            nix_build_user_id_base: default_nix_build_user_id_base(),
281            nix_build_user_count: 32,
282            nix_build_user_prefix: nix_build_user_prefix.to_string(),
283            nix_package_url: None,
284            proxy: Default::default(),
285            extra_conf: Default::default(),
286            force: false,
287            ssl_cert_file: Default::default(),
288            #[cfg(feature = "diagnostics")]
289            diagnostic_attribution: None,
290            #[cfg(feature = "diagnostics")]
291            diagnostic_endpoint: Some("https://install.determinate.systems/nix/diagnostic".into()),
292        })
293    }
294
295    /// A listing of the settings, suitable for [`Planner::settings`](crate::planner::Planner::settings)
296    pub fn settings(&self) -> Result<HashMap<String, serde_json::Value>, InstallSettingsError> {
297        let Self {
298            determinate_nix,
299            modify_profile,
300            nix_build_group_name,
301            nix_build_group_id,
302            nix_build_user_prefix,
303            nix_build_user_id_base,
304            nix_build_user_count,
305            nix_package_url,
306            proxy,
307            extra_conf,
308            force,
309            ssl_cert_file,
310            #[cfg(feature = "diagnostics")]
311                diagnostic_attribution: _,
312            #[cfg(feature = "diagnostics")]
313            diagnostic_endpoint,
314        } = self;
315        let mut map = HashMap::default();
316
317        map.insert(
318            "determinate_nix".into(),
319            serde_json::to_value(determinate_nix)?,
320        );
321        map.insert(
322            "modify_profile".into(),
323            serde_json::to_value(modify_profile)?,
324        );
325        map.insert(
326            "nix_build_group_name".into(),
327            serde_json::to_value(nix_build_group_name)?,
328        );
329        map.insert(
330            "nix_build_group_id".into(),
331            serde_json::to_value(nix_build_group_id)?,
332        );
333        map.insert(
334            "nix_build_user_prefix".into(),
335            serde_json::to_value(nix_build_user_prefix)?,
336        );
337        map.insert(
338            "nix_build_user_id_base".into(),
339            serde_json::to_value(nix_build_user_id_base)?,
340        );
341        map.insert(
342            "nix_build_user_count".into(),
343            serde_json::to_value(nix_build_user_count)?,
344        );
345        map.insert(
346            "nix_package_url".into(),
347            serde_json::to_value(nix_package_url)?,
348        );
349        map.insert("proxy".into(), serde_json::to_value(proxy)?);
350        map.insert("ssl_cert_file".into(), serde_json::to_value(ssl_cert_file)?);
351        map.insert("extra_conf".into(), serde_json::to_value(extra_conf)?);
352        map.insert("force".into(), serde_json::to_value(force)?);
353
354        #[cfg(feature = "diagnostics")]
355        map.insert(
356            "diagnostic_endpoint".into(),
357            serde_json::to_value(diagnostic_endpoint)?,
358        );
359
360        Ok(map)
361    }
362}
363
364async fn linux_detect_systemd_started() -> bool {
365    use std::process::Stdio;
366
367    let mut started = false;
368    if std::path::Path::new("/run/systemd/system").exists() {
369        started = tokio::process::Command::new("systemctl")
370            .arg("status")
371            .stdin(Stdio::null())
372            .stdout(Stdio::null())
373            .stderr(Stdio::null())
374            .status()
375            .await
376            .ok()
377            .map(|exit| exit.success())
378            .unwrap_or(false)
379    }
380
381    // TODO: Other inits
382    started
383}
384
385#[serde_with::serde_as]
386#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
387#[cfg_attr(feature = "cli", derive(clap::Parser))]
388pub struct InitSettings {
389    /// Which init system to configure (if `--init none` Nix will be root-only)
390    #[cfg_attr(feature = "cli", clap(value_parser, long, env = "NIX_INSTALLER_INIT",))]
391    #[cfg_attr(
392        all(target_os = "macos", feature = "cli"),
393        clap(default_value_t = InitSystem::Launchd)
394    )]
395    #[cfg_attr(
396        all(target_os = "linux", feature = "cli"),
397        clap(default_value_t = InitSystem::Systemd)
398    )]
399    pub init: InitSystem,
400
401    /// Start the daemon (if not `--init none`)
402    #[cfg_attr(
403        feature = "cli",
404        clap(
405            value_parser,
406            long,
407            action(ArgAction::SetFalse),
408            env = "NIX_INSTALLER_START_DAEMON",
409            default_value_t = true,
410            long = "no-start-daemon"
411        )
412    )]
413    pub start_daemon: bool,
414}
415
416impl InitSettings {
417    /// The default settings for the given Architecture & Operating System
418    pub async fn default() -> Result<Self, InstallSettingsError> {
419        use target_lexicon::{Architecture, OperatingSystem};
420        let (init, start_daemon) = match (Architecture::host(), OperatingSystem::host()) {
421            (Architecture::X86_64, OperatingSystem::Linux) => {
422                (InitSystem::Systemd, linux_detect_systemd_started().await)
423            },
424            (Architecture::X86_32(_), OperatingSystem::Linux) => {
425                (InitSystem::Systemd, linux_detect_systemd_started().await)
426            },
427            (Architecture::Aarch64(_), OperatingSystem::Linux) => {
428                (InitSystem::Systemd, linux_detect_systemd_started().await)
429            },
430            (Architecture::X86_64, OperatingSystem::MacOSX { .. })
431            | (Architecture::X86_64, OperatingSystem::Darwin) => (InitSystem::Launchd, true),
432            (Architecture::Aarch64(_), OperatingSystem::MacOSX { .. })
433            | (Architecture::Aarch64(_), OperatingSystem::Darwin) => (InitSystem::Launchd, true),
434            _ => {
435                return Err(InstallSettingsError::UnsupportedArchitecture(
436                    target_lexicon::HOST,
437                ))
438            },
439        };
440
441        Ok(Self { init, start_daemon })
442    }
443
444    /// A listing of the settings, suitable for [`Planner::settings`](crate::planner::Planner::settings)
445    pub fn settings(&self) -> Result<HashMap<String, serde_json::Value>, InstallSettingsError> {
446        let Self { init, start_daemon } = self;
447        let mut map = HashMap::default();
448
449        map.insert("init".into(), serde_json::to_value(init)?);
450        map.insert("start_daemon".into(), serde_json::to_value(start_daemon)?);
451        Ok(map)
452    }
453
454    /// Which init system to configure
455    pub fn init(&mut self, init: InitSystem) -> &mut Self {
456        self.init = init;
457        self
458    }
459
460    /// Start the daemon (if one is configured)
461    pub fn start_daemon(&mut self, toggle: bool) -> &mut Self {
462        self.start_daemon = toggle;
463        self
464    }
465}
466
467/// An error originating from a [`Planner::settings`](crate::planner::Planner::settings)
468#[non_exhaustive]
469#[derive(thiserror::Error, Debug, strum::IntoStaticStr)]
470pub enum InstallSettingsError {
471    /// `nix-installer` does not support the architecture right now
472    #[error("`nix-installer` does not support the `{0}` architecture right now")]
473    UnsupportedArchitecture(target_lexicon::Triple),
474    /// Parsing URL
475    #[error("Parsing URL")]
476    Parse(
477        #[source]
478        #[from]
479        url::ParseError,
480    ),
481    /// JSON serialization or deserialization error
482    #[error("JSON serialization or deserialization error")]
483    SerdeJson(
484        #[source]
485        #[from]
486        serde_json::Error,
487    ),
488    #[error("No supported init system found")]
489    InitNotSupported,
490    #[error(transparent)]
491    UrlOrPath(#[from] UrlOrPathError),
492}
493
494#[derive(Debug, thiserror::Error)]
495pub enum UrlOrPathError {
496    #[error("Error parsing URL `{0}`")]
497    Url(String, #[source] url::ParseError),
498    #[error("The specified path `{0}` does not exist")]
499    PathDoesNotExist(PathBuf),
500    #[error("Error fetching URL `{0}`")]
501    Reqwest(Url, #[source] reqwest::Error),
502    #[error("I/O error when accessing `{0}`")]
503    Io(PathBuf, #[source] std::io::Error),
504}
505
506#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Clone)]
507pub enum UrlOrPath {
508    Url(Url),
509    Path(PathBuf),
510}
511
512impl Display for UrlOrPath {
513    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
514        match self {
515            UrlOrPath::Url(url) => f.write_fmt(format_args!("{url}")),
516            UrlOrPath::Path(path) => f.write_fmt(format_args!("{}", path.display())),
517        }
518    }
519}
520
521impl FromStr for UrlOrPath {
522    type Err = UrlOrPathError;
523
524    fn from_str(s: &str) -> Result<Self, Self::Err> {
525        match Url::parse(s) {
526            Ok(url) => Ok(UrlOrPath::Url(url)),
527            Err(url::ParseError::RelativeUrlWithoutBase) => {
528                // This is most likely a relative path (`./boop` or `boop`)
529                // or an absolute path (`/boop`)
530                //
531                // So we'll see if such a path exists, and if so, use it
532                let path = PathBuf::from(s);
533                if path.exists() {
534                    Ok(UrlOrPath::Path(path))
535                } else {
536                    Err(UrlOrPathError::PathDoesNotExist(path))
537                }
538            },
539            Err(e) => Err(UrlOrPathError::Url(s.to_string(), e)),
540        }
541    }
542}
543
544#[cfg(feature = "cli")]
545impl clap::builder::TypedValueParser for UrlOrPath {
546    type Value = UrlOrPath;
547
548    fn parse_ref(
549        &self,
550        cmd: &clap::Command,
551        _arg: Option<&clap::Arg>,
552        value: &std::ffi::OsStr,
553    ) -> Result<Self::Value, clap::Error> {
554        let value_str = value.to_str().ok_or_else(|| {
555            let mut err = clap::Error::new(clap::error::ErrorKind::InvalidValue);
556            err.insert(
557                ContextKind::InvalidValue,
558                ContextValue::String(format!("`{value:?}` not a UTF-8 string")),
559            );
560            err
561        })?;
562        match UrlOrPath::from_str(value_str) {
563            Ok(v) => Ok(v),
564            Err(from_str_error) => {
565                let mut err = clap::Error::new(clap::error::ErrorKind::InvalidValue).with_cmd(cmd);
566                err.insert(
567                    clap::error::ContextKind::Custom,
568                    clap::error::ContextValue::String(from_str_error.to_string()),
569                );
570                Err(err)
571            },
572        }
573    }
574}
575
576#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Clone)]
577pub enum UrlOrPathOrString {
578    Url(Url),
579    Path(PathBuf),
580    String(String),
581}
582
583impl FromStr for UrlOrPathOrString {
584    type Err = url::ParseError;
585
586    fn from_str(s: &str) -> Result<Self, Self::Err> {
587        match Url::parse(s) {
588            Ok(url) => Ok(UrlOrPathOrString::Url(url)),
589            Err(url::ParseError::RelativeUrlWithoutBase) => {
590                // This is most likely a relative path (`./boop` or `boop`)
591                // or an absolute path (`/boop`)
592                //
593                // So we'll see if such a path exists, and if so, use it
594                let path = PathBuf::from(s);
595                if path.exists() {
596                    Ok(UrlOrPathOrString::Path(path))
597                } else {
598                    // The path doesn't exist, so the user is providing us with a string
599                    Ok(UrlOrPathOrString::String(s.into()))
600                }
601            },
602            Err(e) => Err(e),
603        }
604    }
605}
606
607#[cfg(feature = "cli")]
608impl clap::builder::TypedValueParser for UrlOrPathOrString {
609    type Value = UrlOrPathOrString;
610
611    fn parse_ref(
612        &self,
613        cmd: &clap::Command,
614        _arg: Option<&clap::Arg>,
615        value: &std::ffi::OsStr,
616    ) -> Result<Self::Value, clap::Error> {
617        let value_str = value.to_str().ok_or_else(|| {
618            let mut err = clap::Error::new(clap::error::ErrorKind::InvalidValue);
619            err.insert(
620                ContextKind::InvalidValue,
621                ContextValue::String(format!("`{value:?}` not a UTF-8 string")),
622            );
623            err
624        })?;
625        match UrlOrPathOrString::from_str(value_str) {
626            Ok(v) => Ok(v),
627            Err(from_str_error) => {
628                let mut err = clap::Error::new(clap::error::ErrorKind::InvalidValue).with_cmd(cmd);
629                err.insert(
630                    clap::error::ContextKind::Custom,
631                    clap::error::ContextValue::String(from_str_error.to_string()),
632                );
633                Err(err)
634            },
635        }
636    }
637}
638
639#[cfg(feature = "diagnostics")]
640impl crate::diagnostics::ErrorDiagnostic for InstallSettingsError {
641    fn diagnostic(&self) -> String {
642        let static_str: &'static str = (self).into();
643        static_str.to_string()
644    }
645}
646
647#[cfg(test)]
648mod tests {
649    use super::{FromStr, PathBuf, Url, UrlOrPath, UrlOrPathOrString};
650
651    #[test]
652    fn url_or_path_or_string_parses() -> Result<(), Box<dyn std::error::Error>> {
653        assert_eq!(
654            UrlOrPathOrString::from_str("https://boop.bleat")?,
655            UrlOrPathOrString::Url(Url::from_str("https://boop.bleat")?),
656        );
657        assert_eq!(
658            UrlOrPathOrString::from_str("file:///boop/bleat")?,
659            UrlOrPathOrString::Url(Url::from_str("file:///boop/bleat")?),
660        );
661        // The file *must* exist!
662        assert_eq!(
663            UrlOrPathOrString::from_str(file!())?,
664            UrlOrPathOrString::Path(PathBuf::from_str(file!())?),
665        );
666        assert_eq!(
667            UrlOrPathOrString::from_str("Boop")?,
668            UrlOrPathOrString::String(String::from("Boop")),
669        );
670        Ok(())
671    }
672
673    #[test]
674    fn url_or_path_parses() -> Result<(), Box<dyn std::error::Error>> {
675        assert_eq!(
676            UrlOrPath::from_str("https://boop.bleat")?,
677            UrlOrPath::Url(Url::from_str("https://boop.bleat")?),
678        );
679        assert_eq!(
680            UrlOrPath::from_str("file:///boop/bleat")?,
681            UrlOrPath::Url(Url::from_str("file:///boop/bleat")?),
682        );
683        // The file *must* exist!
684        assert_eq!(
685            UrlOrPath::from_str(file!())?,
686            UrlOrPath::Path(PathBuf::from_str(file!())?),
687        );
688        Ok(())
689    }
690}
691
692pub fn determinate_nix_settings() -> nix_config_parser::NixConfig {
693    let mut cfg = nix_config_parser::NixConfig::new();
694    let settings = cfg.settings_mut();
695
696    settings.insert("netrc-file".into(), "/nix/var/determinate/netrc".into());
697    settings.insert(
698        "extra-substituters".into(),
699        "https://cache.flakehub.com".into(),
700    );
701
702    cfg
703}