Commit f666d064 authored by Jeremy Soller's avatar Jeremy Soller

User configuration

parent 1abfd39b
Cargo.lock
target
[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" }
# 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);
}
```
# 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]
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);
}
}
}
#[derive(Debug, Default, Deserialize)]
pub struct GeneralConfig {
pub prompt: bool
}
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>,
}
#[derive(Debug, Default, Deserialize)]
pub struct PackageConfig {
pub version: String,
pub git: String,
pub path: String,
}
#[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>,
}
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(())
}
#[macro_use]
extern crate serde_derive;
pub use config::Config;
pub use install::install;
mod config;
mod install;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment