pax_global_header 0000666 0000000 0000000 00000000064 14576327024 0014524 g ustar 00root root 0000000 0000000 52 comment=3355931339a59d0567972eb8950f8b3e3e9fe9a0
./mnt-reform-setup-wizard/ 0000775 0000000 0000000 00000000000 14576327024 0015747 5 ustar 00root root 0000000 0000000 ./mnt-reform-setup-wizard/.cargo/ 0000775 0000000 0000000 00000000000 14576327024 0017120 5 ustar 00root root 0000000 0000000 ./mnt-reform-setup-wizard/.cargo/config.toml 0000664 0000000 0000000 00000000105 14576327024 0021256 0 ustar 00root root 0000000 0000000 [target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
./mnt-reform-setup-wizard/.gitignore 0000664 0000000 0000000 00000000010 14576327024 0017726 0 ustar 00root root 0000000 0000000 /target
./mnt-reform-setup-wizard/.gitlab-ci.yml 0000664 0000000 0000000 00000001344 14576327024 0020405 0 ustar 00root root 0000000 0000000 image: debian:testing-slim
build-cross:
stage: build
script: |
dpkg --add-architecture arm64
apt-get update -y
apt-get install -y curl
curl https://sh.rustup.rs -sSf | sh -s -- -y
source "$HOME/.cargo/env"
rustup target add aarch64-unknown-linux-gnu
apt-get install -y crossbuild-essential-arm64 libgtk-4-1:arm64 libadwaita-1-0:arm64 libgtk-4-dev:arm64 libadwaita-1-dev:arm64 upx
export PKG_CONFIG_SYSROOT_DIR=/
export PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig/
cargo build --release --target aarch64-unknown-linux-gnu
upx target/aarch64-unknown-linux-gnu/release/reform-setup
artifacts:
when: always
paths:
- target/aarch64-unknown-linux-gnu/release/reform-setup
./mnt-reform-setup-wizard/Cargo.toml 0000664 0000000 0000000 00000000652 14576327024 0017702 0 ustar 00root root 0000000 0000000 [package]
name = "reform-setup"
version = "0.1.0"
edition = "2021"
[profile.release]
strip = true # Automatically strip symbols from the binary.
opt-level = "z" # Optimize for size.
lto = true
[dependencies]
adw = { version = "0.6.0", package = "libadwaita", features = ["v1_4"] }
gtk = { version = "0.8.0", package = "gtk4", features = ["v4_12"] }
gdk-pixbuf = "0.19.2"
regex = "1.10.3"
quick-xml = "0.31.0"
pwhash = "1"
./mnt-reform-setup-wizard/cleanup.sh 0000775 0000000 0000000 00000000500 14576327024 0017730 0 ustar 00root root 0000000 0000000 #!/bin/sh
# This script is run after the setup wizard has completed successfully.
# 1. Remove autostart of reform-setup
rm /etc/profile.d/reform-setup
# 2. Remove root autologin
rm /etc/systemd/system/getty@tty1.service.d/override.conf
# 3. Enable and start greetd
systemctl enable greetd
systemctl start greetd
./mnt-reform-setup-wizard/gfx/ 0000775 0000000 0000000 00000000000 14576327024 0016533 5 ustar 00root root 0000000 0000000 ./mnt-reform-setup-wizard/gfx/setup-wizard-sway.svg 0000664 0000000 0000000 00000016665 14576327024 0022711 0 ustar 00root root 0000000 0000000
./mnt-reform-setup-wizard/gfx/setup-wizard-wayfire.svg 0000664 0000000 0000000 00000031114 14576327024 0023356 0 ustar 00root root 0000000 0000000
./mnt-reform-setup-wizard/src/ 0000775 0000000 0000000 00000000000 14576327024 0016536 5 ustar 00root root 0000000 0000000 ./mnt-reform-setup-wizard/src/main.rs 0000664 0000000 0000000 00000063355 14576327024 0020044 0 ustar 00root root 0000000 0000000 use adw::prelude::*;
use gtk::{glib, ApplicationWindow, Stack, StackSidebar, StackPage, Box, Button, Label, DropDown, StringList, Entry, PasswordEntry, CheckButton, Image};
use adw::{Application, NavigationSplitView, NavigationPage, HeaderBar, ToolbarView, StatusPage};
use gtk::gdk;
use crate::glib::clone;
use std::process::Command;
use regex::Regex;
use quick_xml::reader::Reader as XmlReader;
use quick_xml::events::Event as XmlEvent;
use std::collections::HashMap;
use std::io::Write;
use pwhash::bcrypt;
use std::cell::RefCell;
use std::rc::Rc;
use std::io::BufRead;
const APP_ID: &str = "com.mntre.reform-setup";
const MARGIN: i32 = 48;
const PATH_ETC_DEFAULT_KEYBOARD: &str = "/etc/default/keyboard";
const TEMPLATE_ETC_DEFAULT_KEYBOARD: &str = r#"# KEYBOARD CONFIGURATION FILE
# Consult the keyboard(5) manual page.
XKBMODEL="pc105"
XKBLAYOUT="${LAYOUT}"
XKBVARIANT="${VARIANT}"
XKBOPTIONS=""
BACKSPACE="guess"
"#;
const PATH_ETC_HOSTNAME: &str = "/etc/hostname";
const PATH_ETC_HOSTS: &str = "/etc/hosts";
const TEMPLATE_ETC_HOSTS: &str = r#"127.0.0.1 localhost ${HOSTNAME}
::1 localhost ip6-localhost ip6-loopback ${HOSTNAME}
"#;
const DESKTOP_BINARIES : [&str; 2] = [ "sway", "wayfire" ];
const PATH_ETC_GREETD_CONFIG: &str = "/etc/greetd/config.toml";
const TEMPLATE_ETC_GREETD_CONFIG: &str = r#"[terminal]
# The VT to run the greeter on. Can be "next", "current" or a number
# designating the VT.
vt = 7
# The default session, also known as the greeter.
[default_session]
command = "/usr/bin/tuigreet --window-padding 4 --remember --asterisks --cmd /usr/bin/${DESKTOP}"
# The user to run the command as. The privileges this user must have depends
# on the greeter. A graphical greeter may for example require the user to be
# in the `video` group.
user = "_greetd"
"#;
const PATH_SKEL_SWAY_INPUT : &str = "/etc/skel/.config/sway/config.d/input";
const TEMPLATE_SKEL_SWAY_INPUT: &str = r#"# change to de if you have a QWERTZ keyboard, for example
input * {
xkb_layout ${LAYOUT}
xkb_variant ${VARIANT}
xkb_options lv3:ralt_switch
}
"#;
const PATH_SKEL_WAYFIRE_INI: &str = "/etc/skel/.config/wayfire.ini";
const PATH_CLEANUP_SCRIPTS: &str = "/usr/share/reform-setup-wizard/cleanup.d";
// see also:
// - https://martinber.github.io/gtk-rs.github.io/docs-src/tutorial/closures
// - https://docs.rs/libadwaita/latest/libadwaita
// - https://gtk-rs.org/gtk4-rs/stable/latest/docs/gtk4/
// - https://kerkour.com/rust-cross-compilation
fn valid_password(pw: &str) -> bool {
return pw.len() > 0
}
fn valid_hostname(hostname: &str) -> bool {
let regex = Regex::new(r"^[a-z0-9][a-z0-9-]*$").unwrap();
return hostname.len() > 1 && hostname.len() < 64 && regex.is_match(hostname);
}
fn valid_username(name: &str) -> bool {
// as per https://systemd.io/USER_NAMES/#common-core
return Regex::new(r"^[a-z][a-z0-9-]{0,30}$").unwrap().is_match(name)
}
fn run_command(description: &str, executable: &str, params: Vec<&str>) -> Result {
println!("STEP: {}", &description);
return match Command::new(&executable).args(¶ms).output() {
Ok(output) => {
let stderr = match String::from_utf8(output.stderr.clone()) {
Ok(s) => s,
_ => return Err(format!("Error: {}: {} {:?}: failed to parse utf8 output: {:?}", description, executable, params, &output.stderr))
};
let stdout = match String::from_utf8(output.stdout.clone()) {
Ok(s) => s,
_ => return Err(format!("Error: {}: {} {:?}: failed to parse utf8 output: {:?}", description, executable, params, &output.stdout))
};
println!("STATUS: {:?}", output.status.code());
println!("STDOUT: {}", &stdout);
println!("STDERR: {}", &stderr);
match output.status.code() {
Some(c) if c == 0 => Ok("".to_string()),
_ => Err(format!("Error: {}: {} {:?}: {}", description, executable, params, &stderr))
}
},
Err(e) => Err(format!("Error: {}: {} {:?}: {}", description, executable, params, e))
};
}
fn set_password(description: &str, pw: &str, username: &str) -> Result<(), String> {
let digest = match bcrypt::hash(&pw) {
Ok(v) => v.to_string(),
Err(e) => return Err(format!("Error: Password not hashable: {}", e).to_string())
};
run_command(description, "usermod", ["--password", &digest as &str, username].to_vec())?;
Ok(())
}
fn write_file(path: &str, content: &str) -> Result<(),String> {
match std::path::Path::new(path).parent() {
Some(p) => match std::fs::create_dir_all(p) {
Ok(_) => (),
Err(e) => return Err(format!("Trouble ensuring {}: {}", path, e))
},
_ => ()
};
match std::fs::File::create(path) {
Ok(mut f) => {
match f.write_all(content.as_bytes()) {
Ok(_) => Ok(println!("{} successfully written.", path)),
Err(e) => Err(format!("Trouble writing {}: {}", path, e))
}
},
Err(e) => Err(format!("Trouble opening {} for writing: {}", path, e))
}
}
fn main() -> glib::ExitCode {
let app = Application::builder().application_id(APP_ID).build();
app.connect_activate(build_ui);
app.run()
}
fn build_box() -> Box {
return Box::builder()
.orientation(gtk::Orientation::Vertical)
.margin_top(MARGIN)
.margin_bottom(MARGIN)
.margin_start(MARGIN)
.margin_end(MARGIN)
.spacing(MARGIN/2)
.valign(gtk::Align::Center)
.build();
}
fn build_welcome_page(stack: &Stack) -> StackPage {
let button_next = Button::builder().label("Next").build();
let page_box = build_box();
let status = StatusPage::builder()
.title("Welcome to MNT Reform")
.build();
status.set_vexpand(true);
page_box.append(&status);
page_box.append(&button_next);
button_next.connect_clicked(
clone!(@weak stack => move |_| {
stack.set_visible_child_full("keyboard", gtk::StackTransitionType::SlideLeft);
})
);
return stack.add_titled(&page_box, Some("welcome"), "Welcome");
}
#[derive(Clone)]
struct XkbVariant {
layout: String,
variant: String,
}
fn parse_keyboard_layout_file(file_path: &str) -> HashMap {
let mut layouts = HashMap::::new();
let mut reader = XmlReader::from_file(file_path).unwrap();
let mut in_layout_list = false;
let mut in_config_item = false;
let mut in_name = false;
let mut in_description = false;
let mut in_variant = false;
let mut variant = String::new();
let mut layout = String::new();
let mut description = String::new();
let mut buf = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e),
Ok(XmlEvent::Eof) => break,
Ok(XmlEvent::Start(e)) => {
match e.name().as_ref() {
b"layoutList" => in_layout_list = true,
b"configItem" => in_config_item = true,
b"name" => in_name = true,
b"description" => in_description = true,
b"variant" => in_variant = true,
_ => ()
}
},
Ok(XmlEvent::End(e)) => {
match e.name().as_ref() {
b"layoutList" => in_layout_list = false,
b"layout" => {
layout = "".to_string();
},
b"configItem" => {
description = "".to_string();
variant = "".to_string();
in_config_item = false;
},
b"name" => in_name = false,
b"description" => in_description = false,
b"variant" => in_variant = false,
_ => ()
};
},
Ok(XmlEvent::Text(e)) if in_layout_list && in_config_item => {
let val = e.unescape().unwrap().clone();
if in_name {
if in_variant {
variant = val.to_string();
} else {
layout = val.to_string();
}
} else if in_description {
description = val.to_string();
}
if !layout.is_empty() && !description.is_empty() {
layouts.entry(description.to_string()).or_insert(XkbVariant {
layout: layout.to_string(),
variant: variant.to_string()
});
}
},
_ => ()
}
buf.clear();
}
return layouts;
}
fn build_keyboard_page(stack: &Stack) -> StackPage {
let button_next = Button::builder().label("Next").build();
let page_box = build_box();
let mut layouts_map = parse_keyboard_layout_file("/usr/share/X11/xkb/rules/base.xml");
layouts_map.extend(parse_keyboard_layout_file("/usr/share/X11/xkb/rules/base.extras.xml"));
layouts_map.retain(|_, v| v.layout != "custom");
let mut dropdown_pairs: Vec<(String, XkbVariant)> = layouts_map.into_iter().collect();
dropdown_pairs.sort_by(|a, b| a.0.cmp(&b.0));
let layout_keys: StringList = dropdown_pairs.iter().map(|(k, _)| k.clone()).collect();
let layout_vals: Vec = dropdown_pairs.iter().map(|(_, v)| v.clone()).collect();
let dropdown = DropDown::builder()
.model(&layout_keys)
.enable_search(true)
.build();
let search_exp = gtk::PropertyExpression::new(gtk::StringObject::static_type(), None::, "string");
dropdown.set_expression(Some(search_exp));
dropdown.set_search_match_mode(gtk::StringFilterMatchMode::Substring);
let default_index = layout_vals.iter().position(|v| v.layout == "us" && v.variant == "").unwrap();
dropdown.set_selected(default_index as u32);
page_box.append(&Label::new(Some("Please select your keyboard layout:")));
page_box.append(&dropdown);
let error_label = Label::new(None);
page_box.append(&error_label);
page_box.append(&button_next);
button_next.connect_clicked(
clone!(@weak stack => move |_| {
// TODO: find out to what degree this actually sets the keyboard layout
let layout: &XkbVariant = &layout_vals[dropdown.selected() as usize];
println!("Selected keyboard layout: {:?} variant: {:?}", &layout.layout, &layout.variant);
let new_default = TEMPLATE_ETC_DEFAULT_KEYBOARD.replace("${LAYOUT}", &layout.layout).replace("${VARIANT}", &layout.variant);
match write_file(PATH_ETC_DEFAULT_KEYBOARD, &new_default) {
Ok(_) => (),
Err(e) => {
error_label.set_label(&e);
return;
}
}
let new_sway_input = TEMPLATE_SKEL_SWAY_INPUT.replace("${LAYOUT}", &layout.layout).replace("${VARIANT}", &layout.variant);
match write_file(PATH_SKEL_SWAY_INPUT, &new_sway_input) {
Ok(_) => (),
Err(e) => {
error_label.set_label(&e);
return;
}
}
match run_command("write xkb_layout into wayfire.ini", "sh", ["-c", &format!("sed -i -E 's/^xkb_layout ?=.*$/xkb_layout = {}/g' {}", &layout.layout, PATH_SKEL_WAYFIRE_INI)].to_vec()) {
Ok(_) => (),
Err(e) => {
error_label.set_label(&e);
return
}
};
match run_command("write xkb_variant into wayfire.ini", "sh", ["-c", &format!("sed -i -E 's/^xkb_variant ?=.*$/xkb_variant = {}/g' {}", &layout.variant, PATH_SKEL_WAYFIRE_INI)].to_vec()) {
Ok(_) => (),
Err(e) => {
error_label.set_label(&e);
return
}
};
stack.set_visible_child_full("time", gtk::StackTransitionType::SlideLeft);
})
);
return stack.add_titled(&page_box, Some("keyboard"), "Keyboard Layout");
}
fn build_time_page(stack: &Stack) -> StackPage {
let button_next = Button::builder().label("Next").build();
let page_box = build_box();
// let output = String::from_utf8(Command::new("timedatectl")
// .args(["list-timezones"])
// .output()
// .expect("failed to execute timedatectl").stdout)
// .expect("failed to parse utf8 output of timedatectl");
// NB: above wouldn't run easily within chroot, so for now let's use this
let output = String::from_utf8(Command::new("sh")
.args(["-c", "awk '/^Z/ { print $2 }; /^L/ { print $3 }' /usr/share/zoneinfo/tzdata.zi | sort"])
.output()
.expect("failed to collect timezones").stdout)
.expect("failed to parse utf8 output of awk");
let lines:Vec<&str> = output.lines().collect();
let list = StringList::new(&lines);
let dropdown = DropDown::builder()
.model(&list)
.enable_search(true)
.build();
let search_exp = gtk::PropertyExpression::new(gtk::StringObject::static_type(), None::, "string");
dropdown.set_expression(Some(search_exp));
dropdown.set_search_match_mode(gtk::StringFilterMatchMode::Substring);
let default_index = lines.iter().position(|v| v == &"Europe/Berlin").unwrap();
dropdown.set_selected(default_index as u32);
page_box.append(&Label::new(Some("Please choose your timezone:")));
page_box.append(&dropdown);
let error_label = Label::new(None);
page_box.append(&error_label);
page_box.append(&button_next);
button_next.connect_clicked(
clone!(@weak stack => move |_| {
// in a non-chroot environment we might want to use timedatectl instead
let tz = match list.string(dropdown.selected()) {
Some(s) => s,
_ => {
error_label.set_label("no timezone in dropdown for selection");
return
}
};
match run_command("set timezone", "sh", ["-c", &format!("echo '{}' > /etc/timezone", tz)].to_vec()) {
Ok(_) => (),
Err(e) => {
error_label.set_label(&e);
return
}
};
match run_command("set localtime", "ln", ["-sf", &format!("/usr/share/zoneinfo/{}", tz), "/etc/localtime"].to_vec()) {
Ok(_) => (),
Err(e) => {
error_label.set_label(&e);
return
}
};
stack.set_visible_child_full("desktop", gtk::StackTransitionType::SlideLeft);
})
);
return stack.add_titled(&page_box, Some("time"), "Time");
}
fn image_from_bytes(bytes: &[u8]) -> Image {
let loader = gdk_pixbuf::PixbufLoader::with_type("svg").unwrap();
loader.write(bytes).unwrap();
loader.close().unwrap();
let pixbuf = loader.pixbuf().unwrap();
return gtk::Image::from_paintable(Some(&gdk::Texture::for_pixbuf(&pixbuf)));
}
fn build_desktop_page(stack: &Stack, desktop_index: Rc>) -> StackPage {
let button_next = Button::builder().label("Next").build();
let page_box = build_box();
let choice_sway = CheckButton::builder().label("Sway (Tiling)").build();
let choice_wayfire = CheckButton::builder().label("Wayfire (Traditional)").build();
choice_wayfire.set_group(Some(&choice_sway));
choice_wayfire.set_active(true);
let sway_box = Box::builder()
.orientation(gtk::Orientation::Horizontal)
.valign(gtk::Align::Center)
.build();
let img_sway = image_from_bytes(include_bytes!("../gfx/setup-wizard-sway.svg"));
img_sway.set_size_request(392, 278);
sway_box.append(&img_sway);
sway_box.append(&Label::new(Some("Sway: A tiling desktop with a strong focus on keyboard shortcuts, for advanced users.")));
choice_sway.set_child(Some(&sway_box));
let wayfire_box = Box::builder()
.orientation(gtk::Orientation::Horizontal)
.valign(gtk::Align::Center)
.build();
let img_wayfire = image_from_bytes(include_bytes!("../gfx/setup-wizard-wayfire.svg"));
img_wayfire.set_size_request(392, 278);
wayfire_box.append(&img_wayfire);
wayfire_box.append(&Label::new(Some("Wayfire: A more traditional desktop with overlapping windows, suitable for beginners.")));
choice_wayfire.set_child(Some(&wayfire_box));
page_box.append(&Label::new(Some("Please select your preferred desktop:")));
page_box.append(&choice_wayfire);
page_box.append(&choice_sway);
let error_label = Label::new(None);
page_box.append(&error_label);
page_box.append(&button_next);
button_next.connect_clicked(
clone!(@weak stack => move |_| {
let mut index = desktop_index.borrow_mut();
*index = choice_wayfire.is_active() as usize;
stack.set_visible_child_full("root-password", gtk::StackTransitionType::SlideLeft);
})
);
return stack.add_titled(&page_box, Some("desktop"), "Desktop");
}
fn build_root_pw_page(stack: &Stack) -> StackPage {
let button_next = Button::builder().label("Next").build();
let page_box = build_box();
let pw_entry = PasswordEntry::builder().build();
let pw_entry_rep = PasswordEntry::builder().build();
page_box.append(&Label::new(Some("Please choose a password for the root account:")));
page_box.append(&pw_entry);
page_box.append(&Label::new(Some("And repeat it for safety:")));
page_box.append(&pw_entry_rep);
let error_label = Label::new(None);
page_box.append(&error_label);
page_box.append(&button_next);
button_next.connect_clicked(
clone!(@weak stack => move |_| {
let pw = pw_entry.text();
let pw_retry = pw_entry_rep.text();
if pw != pw_retry {
error_label.set_label("Error: Passwords don't match.");
return;
} else if !valid_password(&pw) {
error_label.set_label(&format!("Error: Password must have at least one character."));
return;
}
match set_password("set root pw", &pw, "root") {
Ok(_) => (),
Err(e) => {
error_label.set_label(&format!("Error: Cannot set password: {}", e));
return
}
};
stack.set_visible_child_full("hostname", gtk::StackTransitionType::SlideLeft);
})
);
return stack.add_titled(&page_box, Some("root-password"), "Root Password");
}
fn build_hostname_page(stack: &Stack) -> StackPage {
let button_next = Button::builder().label("Next").build();
let page_box = build_box();
let hostname_entry = Entry::builder().build();
page_box.append(&Label::new(Some("Please gives this computer a name (lower-case characters only):")));
page_box.append(&hostname_entry);
let error_label = Label::new(None);
page_box.append(&error_label);
page_box.append(&button_next);
button_next.connect_clicked(
clone!(@weak stack => move |_| {
let hostname = hostname_entry.text();
if !valid_hostname(&hostname) {
error_label.set_label("Error: Invalid hostname format.");
return;
}
match write_file(PATH_ETC_HOSTNAME, &hostname) {
Ok(_) => (),
Err(e) => {
error_label.set_label(&format!("Error: Cannot write {}: {}", PATH_ETC_HOSTNAME, e));
return
}
};
let head_hosts = TEMPLATE_ETC_HOSTS.replace("${HOSTNAME}", &hostname);
let hosts_file = match std::fs::File::open(PATH_ETC_HOSTS) {
Ok(f) => f,
Err(e) => {
error_label.set_label(&format!("Error: Cannot write {}: {}", PATH_ETC_HOSTNAME, e));
return
}
};
let reader = std::io::BufReader::new(hosts_file);
let tail_hosts = match reader.lines().skip(2).
map(|result| result.map(|line| line + "\n")).
collect::>() {
Ok(s) => s,
Err(e) => {
error_label.set_label(&format!("Error: Cannot properly read {}: {}", PATH_ETC_HOSTNAME, e));
return
}
};
let to_join = [head_hosts, tail_hosts];
match write_file(PATH_ETC_HOSTS, &to_join.join("\n")) {
Ok(_) => (),
Err(e) => {
error_label.set_label(&format!("Error: Cannot update {}: {}", PATH_ETC_HOSTS, e));
return
}
}
stack.set_visible_child_full("account", gtk::StackTransitionType::SlideLeft);
})
);
return stack.add_titled(&page_box, Some("hostname"), "Computer Name");
}
fn build_account_page(stack: &Stack, desktop_index: Rc>, window: &ApplicationWindow) -> StackPage {
let button_next = Button::builder().label("Next").build();
let page_box = build_box();
let username_entry = Entry::builder().build();
let pw_entry = PasswordEntry::builder().build();
let pw_entry_rep = PasswordEntry::builder().build();
page_box.append(&Label::new(Some("Please choose a username for your personal account:")));
page_box.append(&username_entry);
page_box.append(&Label::new(Some("And a password for your account:")));
page_box.append(&pw_entry);
page_box.append(&Label::new(Some("And repeat it for safety:")));
page_box.append(&pw_entry_rep);
let error_label = Label::new(None);
page_box.append(&error_label);
page_box.append(&button_next);
let cleanup_script = format!("for script in `ls -1 {}`; do \"{}/$script\"; done", PATH_CLEANUP_SCRIPTS, PATH_CLEANUP_SCRIPTS);
button_next.connect_clicked(
clone!(@weak window => move |_| {
let username = username_entry.text();
if !valid_username(&username) {
error_label.set_label("Error: Invalid username format.");
return;
}
let pw = pw_entry.text();
let pw_retry = pw_entry_rep.text();
if pw != pw_retry {
error_label.set_label("Error: Passwords don't match.");
return;
} else if !valid_password(&pw) {
error_label.set_label("Error: Password must have at least one character.");
return;
}
match run_command("adduser", "adduser", ["--disabled-password", "--comment", "", &username].to_vec()) {
Ok(_) => (),
Err(e) => {
error_label.set_label(&e);
return
}
};
match run_command("add to sudoers", "usermod", ["-aG", "sudo", &username].to_vec()) {
Ok(_) => (),
Err(e) => {
error_label.set_label(&e);
return
}
};
let greetd_config = TEMPLATE_ETC_GREETD_CONFIG.replace("${DESKTOP}", &DESKTOP_BINARIES[*desktop_index.borrow()]);
match write_file(PATH_ETC_GREETD_CONFIG, &greetd_config) {
Ok(_) => (),
Err(e) => {
error_label.set_label(&e);
return;
}
}
match set_password("set user pw", &pw, &username) {
Ok(_) => (),
Err(e) => {
error_label.set_label(&format!("Error: Cannot set password: {}", e));
return
}
};
match run_command("run cleanup scripts", "sh", ["-c", &cleanup_script].to_vec()) {
Ok(_) => (),
Err(e) => {
error_label.set_label(&e);
return
}
};
let _ = window.close();
})
);
return stack.add_titled(&page_box, Some("account"), "Account");
}
fn build_ui(app: &Application) {
let header_bar = HeaderBar::default();
let stack = Stack::builder().build();
let toolbar_view = ToolbarView::builder()
.content(&stack)
.build();
let content_page = NavigationPage::builder()
.title("Content")
.child(&toolbar_view)
.build();
// navigation sidebar
let sidebar = StackSidebar::builder()
.stack(&stack)
.build();
let nav_page = NavigationPage::builder()
.title("Sidebar")
.child(&sidebar)
.build();
// split view
let nav_view = NavigationSplitView::builder()
.sidebar(&nav_page)
.content(&content_page)
.build();
let window = ApplicationWindow::builder()
.application(app)
.default_width(640)
.default_height(480)
.title("MNT Reform Setup Wizard")
.child(&nav_view)
.build();
window.set_titlebar(Some(&header_bar));
window.present();
let desktop_index = Rc::new(RefCell::new(0));
// main content stack
let _welcome_page = build_welcome_page(&stack);
let _keyboard_page = build_keyboard_page(&stack);
let _time_page = build_time_page(&stack);
let _desktop_page = build_desktop_page(&stack, desktop_index.clone());
let _root_pw_page = build_root_pw_page(&stack);
let _hostname_page = build_hostname_page(&stack);
let _account_page = build_account_page(&stack, desktop_index.clone(), &window);
}
./mnt-reform-setup-wizard/test_with_chroot.sh 0000775 0000000 0000000 00000003045 14576327024 0021700 0 ustar 00root root 0000000 0000000 #!/bin/sh
set -e
# set up variables and check existence of chroot system
if [ -z "${CHROOT_PATH}" ]; then
echo "Need to set CHROOT_PATH!"
exit 1
fi
wizard_filename="reform-setup"
cleanup_script_filename="cleanup.sh"
repo_path_wizard="./target/debug/${wizard_filename}"
repo_path_cleanup_script="./${cleanup_script_filename}"
chroot_dir_root="${CHROOT_PATH}/root"
chroot_path_wizard="./root/${wizard_filename}"
if [ ! -d "${chroot_dir_root}" ]; then
echo "Can't find ${chroot_dir_root}, suspecting no system for chrooting set up."
echo "To set it up, run something like this:"
echo "# mkdir -p ${CHROOT_PATH}"
echo "# debootstrap unstable ${CHROOT_PATH} http://deb.debian.org/debian/"
exit 1
fi
# ensure the current variants of the needed executables are inside the chrooted system
sudo cp "${repo_path_wizard}" "${chroot_dir_root}/"
sudo cp "${repo_path_cleanup_script}" "${chroot_dir_root}/"
# packages needed for GTK4
sudo chroot "${CHROOT_PATH}" /bin/apt remove -y libssl3
sudo chroot "${CHROOT_PATH}" /bin/apt install -y libgtk-4-1 libadwaita-1-0
# packages needed for keyboard configuration (?)
sudo chroot "${CHROOT_PATH}" /bin/apt install -y keyboard-configuration locales
sudo chroot "${CHROOT_PATH}" /bin/sh -c 'echo locales locales/locales_to_be_generated multiselect en_US.UTF-8 | debconf-set-selections'
sudo chroot "${CHROOT_PATH}" dpkg-reconfigure --frontend noninteractive locales
# xhost +/- allows the Wizard to access the X Server for display
xhost +
sudo chroot "${CHROOT_PATH}" "${chroot_path_wizard}"
xhost -
exit 0