User configuration
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
Cargo.lock
|
||||
target
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "redox_installer"
|
||||
version = "0.1.0"
|
||||
|
||||
[[bin]]
|
||||
name = "redox_installer"
|
||||
path = "src/bin/installer.rs"
|
||||
|
||||
[lib]
|
||||
name = "redox_installer"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
liner = "0.1"
|
||||
rand = "0.3"
|
||||
serde = "0.8"
|
||||
serde_derive = "0.8"
|
||||
termion = "1.1"
|
||||
toml = { version = "0.2", default-features = false, features = ["serde"] }
|
||||
userutils = { git = "https://github.com/redox-os/userutils.git" }
|
||||
@@ -1 +1,55 @@
|
||||
# installer
|
||||
# Redox OS installer
|
||||
|
||||
The Redox installer will allow you to produce a Redox OS image. You will
|
||||
be able to specify:
|
||||
- Output device (raw image, ISO, QEMU, VirtualBox, drive)
|
||||
- Filesystem
|
||||
- Included packages
|
||||
- Method of installation (from source, from binary)
|
||||
- User accounts
|
||||
|
||||
You will be prompted to install dependencies, based on your OS and method of
|
||||
installation. The easiest method is to install from binaries.
|
||||
|
||||
## Usage
|
||||
|
||||
It is recommended to compile with `cargo`, in release mode:
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
By default, you will be prompted to supply configuration options. You can
|
||||
use the scripted mode by supplying a configuration file:
|
||||
```bash
|
||||
cargo run --release -- config/example.toml
|
||||
```
|
||||
An example configuration can be found in [config/example.toml](./config/example.toml).
|
||||
Unsuplied configuration will use the default. You can use the `general.prompt`
|
||||
setting to prompt when configuration is not set. Multiple configurations can
|
||||
be specified, they will be built in order.
|
||||
|
||||
## Embedding
|
||||
|
||||
The installer can also be used inside of other crates, as a library:
|
||||
|
||||
```toml
|
||||
# Cargo.toml
|
||||
[dependencies]
|
||||
redox_installer = "0.1"
|
||||
```
|
||||
|
||||
```rust
|
||||
// src/main.rs
|
||||
extern crate redox_installer;
|
||||
|
||||
use std::io;
|
||||
|
||||
fn main() {
|
||||
let stdout = io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
|
||||
let mut config = redox_installer::Config::default();
|
||||
...
|
||||
redox_installer::install(&mut stdout, &config);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# This is an example configuration file
|
||||
|
||||
# General settings
|
||||
[general]
|
||||
# Prompt if settings are not defined
|
||||
prompt = true
|
||||
|
||||
# Package settings
|
||||
[packages]
|
||||
orbutils = {}
|
||||
|
||||
# User settings
|
||||
[users.root]
|
||||
uid = 0
|
||||
gid = 0
|
||||
|
||||
[users.user]
|
||||
@@ -0,0 +1,65 @@
|
||||
extern crate redox_installer;
|
||||
extern crate serde;
|
||||
extern crate toml;
|
||||
|
||||
use std::{env, process};
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read, Write};
|
||||
|
||||
fn main() {
|
||||
let stderr = io::stderr();
|
||||
let mut stderr = stderr.lock();
|
||||
|
||||
let mut configs = vec![];
|
||||
for arg in env::args().skip(1) {
|
||||
match File::open(&arg) {
|
||||
Ok(mut config_file) => {
|
||||
let mut config_data = String::new();
|
||||
match config_file.read_to_string(&mut config_data) {
|
||||
Ok(_) => {
|
||||
let mut parser = toml::Parser::new(&config_data);
|
||||
match parser.parse() {
|
||||
Some(parsed) => {
|
||||
let mut decoder = toml::Decoder::new(toml::Value::Table(parsed));
|
||||
match serde::Deserialize::deserialize(&mut decoder) {
|
||||
Ok(config) => {
|
||||
configs.push(config);
|
||||
},
|
||||
Err(err) => {
|
||||
writeln!(stderr, "installer: {}: failed to decode: {}", arg, err).unwrap();
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {
|
||||
for error in parser.errors {
|
||||
writeln!(stderr, "installer: {}: failed to parse: {}", arg, error).unwrap();
|
||||
}
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
writeln!(stderr, "installer: {}: failed to read: {}", arg, err).unwrap();
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
writeln!(stderr, "installer: {}: failed to open: {}", arg, err).unwrap();
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if configs.is_empty() {
|
||||
configs.push(redox_installer::Config::default());
|
||||
}
|
||||
|
||||
for config in configs {
|
||||
if let Err(err) = redox_installer::install(config) {
|
||||
writeln!(stderr, "installer: failed to install: {}", err).unwrap();
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
pub struct GeneralConfig {
|
||||
pub prompt: bool
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
mod general;
|
||||
mod package;
|
||||
mod user;
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
pub struct Config {
|
||||
pub general: general::GeneralConfig,
|
||||
pub packages: BTreeMap<String, package::PackageConfig>,
|
||||
pub users: BTreeMap<String, user::UserConfig>,
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
pub struct PackageConfig {
|
||||
pub version: String,
|
||||
pub git: String,
|
||||
pub path: String,
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
pub struct UserConfig {
|
||||
pub password: Option<String>,
|
||||
pub uid: Option<u32>,
|
||||
pub gid: Option<u32>,
|
||||
pub name: Option<String>,
|
||||
pub home: Option<String>,
|
||||
pub shell: Option<String>,
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
extern crate liner;
|
||||
extern crate rand;
|
||||
extern crate termion;
|
||||
extern crate userutils;
|
||||
|
||||
use self::rand::Rng;
|
||||
use self::termion::input::TermRead;
|
||||
|
||||
use std::io::{self, Write};
|
||||
use std::str::FromStr;
|
||||
|
||||
use config::Config;
|
||||
|
||||
fn unwrap_or_prompt<T: FromStr>(option: Option<T>, context: &mut liner::Context, prompt: &str) -> Result<T, String> {
|
||||
match option {
|
||||
None => {
|
||||
let line = context.read_line(prompt, &mut |_| {}).map_err(|err| format!("failed to read line: {}", err))?;
|
||||
T::from_str(&line).map_err(|_| format!("failed to parse '{}'", line))
|
||||
},
|
||||
Some(t) => Ok(t)
|
||||
}
|
||||
}
|
||||
|
||||
fn prompt_password(prompt: &str, confirm_prompt: &str) -> Result<String, String> {
|
||||
let stdin = io::stdin();
|
||||
let mut stdin = stdin.lock();
|
||||
let stdout = io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
|
||||
stdout.write(prompt.as_bytes()).map_err(|err| format!("failed to write to stdout: {}", err))?;
|
||||
stdout.flush().map_err(|err| format!("failed to flush stdout: {}", err))?;
|
||||
if let Some(password) = stdin.read_passwd(&mut stdout).map_err(|err| format!("failed to read password: {}", err))? {
|
||||
stdout.write(b"\n").map_err(|err| format!("failed to write to stdout: {}", err))?;
|
||||
stdout.flush().map_err(|err| format!("failed to flush stdout: {}", err))?;
|
||||
|
||||
if password.is_empty() {
|
||||
Ok(password)
|
||||
} else {
|
||||
stdout.write(confirm_prompt.as_bytes()).map_err(|err| format!("failed to write to stdout: {}", err))?;
|
||||
stdout.flush().map_err(|err| format!("failed to flush stdout: {}", err))?;
|
||||
if let Some(confirm_password) = stdin.read_passwd(&mut stdout).map_err(|err| format!("failed to read password: {}", err))? {
|
||||
stdout.write(b"\n").map_err(|err| format!("failed to write to stdout: {}", err))?;
|
||||
stdout.flush().map_err(|err| format!("failed to flush stdout: {}", err))?;
|
||||
|
||||
if confirm_password == password {
|
||||
let salt = format!("{:X}", rand::OsRng::new().unwrap().next_u64());
|
||||
Ok(userutils::Passwd::encode(&password, &salt))
|
||||
} else {
|
||||
Err("passwords do not match".to_string())
|
||||
}
|
||||
} else {
|
||||
Err("passwords do not match".to_string())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(String::new())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn install(config: Config) -> Result<(), String> {
|
||||
println!("Install {:#?}", config);
|
||||
|
||||
let mut context = liner::Context::new();
|
||||
|
||||
macro_rules! prompt {
|
||||
($dst:expr, $($arg:tt)*) => (if config.general.prompt {
|
||||
unwrap_or_prompt($dst, &mut context, &format!($($arg)*))
|
||||
} else {
|
||||
Ok($dst.unwrap_or_default())
|
||||
})
|
||||
}
|
||||
|
||||
macro_rules! prompt_default {
|
||||
($dst:expr, $def:expr, $($arg:tt)*) => (if config.general.prompt {
|
||||
match unwrap_or_prompt($dst, &mut context, &format!($($arg)*)) {
|
||||
Ok(res) => if res.is_empty() {
|
||||
Ok($def)
|
||||
} else {
|
||||
Ok(res)
|
||||
},
|
||||
Err(err) => Err(err)
|
||||
}
|
||||
} else {
|
||||
Ok($def)
|
||||
})
|
||||
}
|
||||
|
||||
let mut passwd = String::new();
|
||||
|
||||
let mut next_uid = 1000;
|
||||
for (username, user) in config.users {
|
||||
let password = if let Some(password) = user.password {
|
||||
password
|
||||
} else if config.general.prompt {
|
||||
prompt_password(&format!("{}: enter password: ", username), &format!("{}: confirm password: ", username))?
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
let uid = user.uid.unwrap_or(next_uid);
|
||||
|
||||
if uid >= next_uid {
|
||||
next_uid = uid + 1;
|
||||
}
|
||||
|
||||
let gid = user.gid.unwrap_or(uid);
|
||||
|
||||
let name = prompt_default!(user.name, username.clone(), "{}: name: ", username)?;
|
||||
let home = prompt_default!(user.home, format!("/home/{}", username), "{}: home: ", username)?;
|
||||
let shell = prompt_default!(user.shell, "/bin/ion".to_string(), "{}: shell: ", username)?;
|
||||
|
||||
println!("Creating user {}:", username);
|
||||
println!("\tPassword: {}", password);
|
||||
println!("\tUID: {}", uid);
|
||||
println!("\tGID: {}", gid);
|
||||
println!("\tName: {}", name);
|
||||
println!("\tHome: {}", home);
|
||||
println!("\tShell: {}", shell);
|
||||
|
||||
passwd.push_str(&format!("{};{};{};{};{};{};{}\n", username, password, uid, gid, name, home, shell));
|
||||
}
|
||||
|
||||
print!("/etc/passwd:\n{}", passwd);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
pub use config::Config;
|
||||
pub use install::install;
|
||||
|
||||
mod config;
|
||||
mod install;
|
||||
Reference in New Issue
Block a user