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 confstr = match env::consts::OS {
54            "illumos" => include_str!("../config/illumos.lua"),
55            "linux" => include_str!("../config/linux.lua"),
56            "macos" => include_str!("../config/macos.lua"),
57            "netbsd" => include_str!("../config/netbsd.lua"),
58            "solaris" => include_str!("../config/illumos.lua"),
59            os => {
60                eprintln!(
61                    "WARNING: OS '{}' not explicitly supported, using generic config",
62                    os
63                );
64                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            if let Some(content) = Scripts::get(&script) {
76                let fp = initdir.join("scripts").join(&*script);
77                fs::create_dir_all(fp.parent().unwrap())?;
78                fs::write(&fp, content.data)?;
79                #[cfg(unix)]
80                {
81                    let mut perms = fs::metadata(&fp)?.permissions();
82                    perms.set_mode(0o755);
83                    fs::set_permissions(&fp, perms)?;
84                }
85                println!("\t{}", fp.display());
86            }
87        }
88
89        Ok(())
90    }
91}