Skip to main content

bob/
init.rs

1/*
2 * Copyright (c) 2025 Jonathan Perkin <jonathan@perkin.org.uk>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17use anyhow::bail;
18use rust_embed::RustEmbed;
19use std::env;
20use std::fs;
21#[cfg(unix)]
22use std::os::unix::fs::PermissionsExt;
23use std::path::PathBuf;
24
25#[derive(Debug, RustEmbed)]
26#[folder = "scripts/"]
27struct Scripts;
28
29pub struct Init {}
30
31impl Init {
32    pub fn create(dir: &PathBuf) -> anyhow::Result<()> {
33        let initdir = if dir.is_absolute() {
34            dir.to_path_buf()
35        } else {
36            env::current_dir()?.join(dir)
37        };
38        if initdir.exists() {
39            if !initdir.is_dir() {
40                bail!("{} exists and is not a directory", initdir.display());
41            }
42            if fs::read_dir(&initdir)?.next().is_some() {
43                bail!("{} exists and is not empty", initdir.display());
44            }
45        }
46
47        let Some(initdir_str) = initdir.to_str() else {
48            bail!("Sorry, configuration directory must be valid UTF-8");
49        };
50
51        println!("Initialising new configuration directory {}:", initdir_str);
52
53        let (current_os, confstr) = match env::consts::OS {
54            "illumos" => ("illumos", include_str!("../config/illumos.lua")),
55            "linux" => ("linux", include_str!("../config/linux.lua")),
56            "macos" => ("macos", include_str!("../config/macos.lua")),
57            "netbsd" => ("netbsd", include_str!("../config/netbsd.lua")),
58            "solaris" => ("illumos", include_str!("../config/illumos.lua")),
59            os => {
60                eprintln!(
61                    "WARNING: OS '{}' not explicitly supported, using generic config",
62                    os
63                );
64                (os, include_str!("../config/generic.lua"))
65            }
66        };
67
68        let confstr = confstr.replace("@INITDIR@", initdir_str);
69        let conffile = initdir.join("config.lua");
70        fs::create_dir_all(conffile.parent().unwrap())?;
71        fs::write(&conffile, confstr)?;
72        println!("\t{}", conffile.display());
73
74        for script in Scripts::iter() {
75            let script_path = PathBuf::from(&*script);
76            let components: Vec<_> = script_path.components().collect();
77
78            /*
79             * Scripts may be placed in OS-specific subdirectories (e.g.,
80             * scripts/macos/foo).  If present, the first path component is
81             * compared against the current OS and skipped if it doesn't match.
82             * Matching scripts have the OS prefix stripped from the destination.
83             */
84            let dest = if components.len() > 1 {
85                if components[0].as_os_str().to_str() != Some(current_os) {
86                    continue;
87                }
88                components[1..].iter().collect()
89            } else {
90                script_path
91            };
92
93            if let Some(content) = Scripts::get(&script) {
94                let fp = initdir.join("scripts").join(&dest);
95                fs::create_dir_all(fp.parent().unwrap())?;
96                fs::write(&fp, content.data)?;
97                #[cfg(unix)]
98                {
99                    let mut perms = fs::metadata(&fp)?.permissions();
100                    perms.set_mode(0o755);
101                    fs::set_permissions(&fp, perms)?;
102                }
103                println!("\t{}", fp.display());
104            }
105        }
106
107        Ok(())
108    }
109}