From ac46b45f05fb2a6f0ff3a233152b8840b6e22469 Mon Sep 17 00:00:00 2001 From: David McKinney Date: Thu, 8 Oct 2020 09:15:40 -0400 Subject: [PATCH] Implemented an installer user interface and backend --- citadel-installer-ui/.gitignore | 1 + citadel-installer-ui/Cargo.lock | 750 ++++++++++++++++++ citadel-installer-ui/Cargo.toml | 15 + citadel-installer-ui/README.md | 23 + .../data/citadel_password_page.ui | 126 +++ .../data/confirm_install_page.ui | 101 +++ .../data/install_destination_page.ui | 80 ++ citadel-installer-ui/data/install_page.ui | 88 ++ .../data/luks_password_page.ui | 126 +++ citadel-installer-ui/data/style.css | 22 + citadel-installer-ui/data/welcome_page.ui | 58 ++ citadel-installer-ui/src/builder.rs | 48 ++ citadel-installer-ui/src/dbus_client.rs | 207 +++++ citadel-installer-ui/src/error.rs | 11 + citadel-installer-ui/src/main.rs | 43 + citadel-installer-ui/src/rowdata.rs | 156 ++++ citadel-installer-ui/src/ui.rs | 420 ++++++++++ citadel-tool/Cargo.toml | 5 +- citadel-tool/src/install/cli.rs | 29 +- citadel-tool/src/install/installer.rs | 54 +- citadel-tool/src/install_backend/dbus.rs | 278 +++++++ citadel-tool/src/install_backend/disk.rs | 80 ++ citadel-tool/src/install_backend/mod.rs | 24 + citadel-tool/src/main.rs | 3 + data/com.subgraph.installer.Manager.conf | 19 + 25 files changed, 2749 insertions(+), 18 deletions(-) create mode 100644 citadel-installer-ui/.gitignore create mode 100644 citadel-installer-ui/Cargo.lock create mode 100644 citadel-installer-ui/Cargo.toml create mode 100644 citadel-installer-ui/README.md create mode 100644 citadel-installer-ui/data/citadel_password_page.ui create mode 100644 citadel-installer-ui/data/confirm_install_page.ui create mode 100644 citadel-installer-ui/data/install_destination_page.ui create mode 100644 citadel-installer-ui/data/install_page.ui create mode 100644 citadel-installer-ui/data/luks_password_page.ui create mode 100644 citadel-installer-ui/data/style.css create mode 100644 citadel-installer-ui/data/welcome_page.ui create mode 100644 citadel-installer-ui/src/builder.rs create mode 100644 citadel-installer-ui/src/dbus_client.rs create mode 100644 citadel-installer-ui/src/error.rs create mode 100644 citadel-installer-ui/src/main.rs create mode 100644 citadel-installer-ui/src/rowdata.rs create mode 100644 citadel-installer-ui/src/ui.rs create mode 100644 citadel-tool/src/install_backend/dbus.rs create mode 100644 citadel-tool/src/install_backend/disk.rs create mode 100644 citadel-tool/src/install_backend/mod.rs create mode 100644 data/com.subgraph.installer.Manager.conf diff --git a/citadel-installer-ui/.gitignore b/citadel-installer-ui/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/citadel-installer-ui/.gitignore @@ -0,0 +1 @@ +/target diff --git a/citadel-installer-ui/Cargo.lock b/citadel-installer-ui/Cargo.lock new file mode 100644 index 0000000..7bc8e4c --- /dev/null +++ b/citadel-installer-ui/Cargo.lock @@ -0,0 +1,750 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "addr2line" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" + +[[package]] +name = "anyhow" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b" + +[[package]] +name = "atk" +version = "0.9.0" +source = "git+https://github.com/gtk-rs/atk#9e3eb26374a4156297280769bd64102a4bebcad7" +dependencies = [ + "atk-sys", + "bitflags", + "glib", + "glib-sys", + "gobject-sys", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.10.0" +source = "git+https://github.com/gtk-rs/sys#56e03c021c393e1cf3f148005b348ac5a8a0ab72" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "backtrace" +version = "0.3.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46254cf2fdcdf1badb5934448c1bcbe046a56537b3987d96c51a7afc5d03f293" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "cairo-rs" +version = "0.9.0" +source = "git+https://github.com/gtk-rs/cairo#2d5a1cb0003176224004c4e826929520bd1227f9" +dependencies = [ + "bitflags", + "cairo-sys-rs", + "glib", + "glib-sys", + "gobject-sys", + "libc", + "thiserror", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.10.0" +source = "git+https://github.com/gtk-rs/cairo#2d5a1cb0003176224004c4e826929520bd1227f9" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "cc" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "citadel-installer" +version = "0.1.0" +dependencies = [ + "dbus", + "failure", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "glib-sys", + "gtk", +] + +[[package]] +name = "dbus" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cd9e78c210146a1860f897db03412fd5091fd73100778e43ee255cca252cf32" +dependencies = [ + "libc", + "libdbus-sys", +] + +[[package]] +name = "either" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56b59865bce947ac5958779cfa508f6c3b9497cc762b7e24a12d11ccde2c4f" + +[[package]] +name = "failure" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" +dependencies = [ + "backtrace", + "failure_derive", +] + +[[package]] +name = "failure_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "futures" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" + +[[package]] +name = "futures-executor" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" + +[[package]] +name = "futures-macro" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" + +[[package]] +name = "futures-task" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" +dependencies = [ + "once_cell", +] + +[[package]] +name = "futures-util" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] + +[[package]] +name = "gdk" +version = "0.13.0" +source = "git+https://github.com/gtk-rs/gdk#fa2fb7819b13daa4fac55bd93ac217691cbd9dc8" +dependencies = [ + "bitflags", + "cairo-rs", + "cairo-sys-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.9.0" +source = "git+https://github.com/gtk-rs/gdk-pixbuf#2502779ebc0a81c8a03a64e1fbf576b16eb8b91d" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "libc", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.10.0" +source = "git+https://github.com/gtk-rs/sys#56e03c021c393e1cf3f148005b348ac5a8a0ab72" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.10.0" +source = "git+https://github.com/gtk-rs/sys#56e03c021c393e1cf3f148005b348ac5a8a0ab72" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gimli" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" + +[[package]] +name = "gio" +version = "0.9.0" +source = "git+https://github.com/gtk-rs/gio#809e580c56f1434327a3c81af49cb75baea5faf7" +dependencies = [ + "bitflags", + "futures", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "libc", + "once_cell", + "thiserror", +] + +[[package]] +name = "gio-sys" +version = "0.10.0" +source = "git+https://github.com/gtk-rs/sys#56e03c021c393e1cf3f148005b348ac5a8a0ab72" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "glib" +version = "0.10.0" +source = "git+https://github.com/gtk-rs/glib#200d34eab3421b53d0688f02093932e14540f411" +dependencies = [ + "bitflags", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "once_cell", +] + +[[package]] +name = "glib-macros" +version = "0.10.0" +source = "git+https://github.com/gtk-rs/glib#200d34eab3421b53d0688f02093932e14540f411" +dependencies = [ + "anyhow", + "heck", + "itertools", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "glib-sys" +version = "0.10.0" +source = "git+https://github.com/gtk-rs/sys#56e03c021c393e1cf3f148005b348ac5a8a0ab72" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "gobject-sys" +version = "0.10.0" +source = "git+https://github.com/gtk-rs/sys#56e03c021c393e1cf3f148005b348ac5a8a0ab72" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.9.0" +source = "git+https://github.com/gtk-rs/gtk#9b9cf0f623bd74bf71459f639beac2f6f26c163e" +dependencies = [ + "atk", + "bitflags", + "cairo-rs", + "cairo-sys-rs", + "cc", + "gdk", + "gdk-pixbuf", + "gdk-pixbuf-sys", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk-sys", + "libc", + "once_cell", + "pango", + "pango-sys", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.10.0" +source = "git+https://github.com/gtk-rs/sys#56e03c021c393e1cf3f148005b348ac5a8a0ab72" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "itertools" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +dependencies = [ + "either", +] + +[[package]] +name = "libc" +version = "0.2.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2f02823cf78b754822df5f7f268fb59822e7296276d3e069d8e8cb26a14bd10" + +[[package]] +name = "libdbus-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc12a3bc971424edbbf7edaf6e5740483444db63aa8e23d3751ff12a30f306f0" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" + +[[package]] +name = "miniz_oxide" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0f75932c1f6cfae3c04000e40114adf955636e19040f9c0a2c380702aa1c7f" +dependencies = [ + "adler", +] + +[[package]] +name = "object" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" + +[[package]] +name = "once_cell" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" + +[[package]] +name = "pango" +version = "0.9.0" +source = "git+https://github.com/gtk-rs/pango#b98784c9881f0def29c8e3bda6d2754e41f96b0f" +dependencies = [ + "bitflags", + "glib", + "glib-sys", + "gobject-sys", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.10.0" +source = "git+https://github.com/gtk-rs/sys#56e03c021c393e1cf3f148005b348ac5a8a0ab72" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "pin-project" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca4433fff2ae79342e497d9f8ee990d174071408f28f726d6d83af93e58e48aa" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c0e815c3ee9a031fdf5af21c10aa17c573c9c6a566328d99e3936c34e36461f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33" + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99c605b9a0adc77b7211c6b1f722dcb613d68d66859a44f3d485a6da332b0598" + +[[package]] +name = "proc-macro-nested" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" + +[[package]] +name = "proc-macro2" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" + +[[package]] +name = "serde" +version = "1.0.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e54c9a88f2da7238af84b5101443f0c0d0a3bbdc455e34a5c9497b1903ed55d5" + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "strum" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57bd81eb48f4c437cadc685403cad539345bf703d78e63707418431cecd4522b" + +[[package]] +name = "strum_macros" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c85aa3f8ea653bfd3ddf25f7ee357ee4d204731f6aa9ad04002306f6e2774c" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e69abc24912995b3038597a7a593be5053eb0fb44f3cc5beec0deb421790c1f4" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "synstructure" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "system-deps" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3ecc17269a19353b3558b313bba738b25d82993e30d62a18406a24aba4649b" +dependencies = [ + "heck", + "pkg-config", + "strum", + "strum_macros", + "thiserror", + "toml", + "version-compare", +] + +[[package]] +name = "thiserror" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" +dependencies = [ + "serde", +] + +[[package]] +name = "unicode-segmentation" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "version-compare" +version = "0.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d63556a25bae6ea31b52e640d7c41d1ab27faba4ccb600013837a3d0b3994ca1" + +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" diff --git a/citadel-installer-ui/Cargo.toml b/citadel-installer-ui/Cargo.toml new file mode 100644 index 0000000..363847b --- /dev/null +++ b/citadel-installer-ui/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "citadel-installer-ui" +version = "0.1.0" +authors = ["David McKinney "] +edition = "2018" + +[dependencies] +libcitadel = { path = "../libcitadel" } +failure = "0.1.8" +dbus = "0.8.4" +gtk = { version = "0.9.0", features = ["v3_16"] } +gio = "0.9.1" +glib = "0.10.2" +gdk = "0.13.2" +pango = "0.9.1" diff --git a/citadel-installer-ui/README.md b/citadel-installer-ui/README.md new file mode 100644 index 0000000..a94433a --- /dev/null +++ b/citadel-installer-ui/README.md @@ -0,0 +1,23 @@ +# Citadel Installer UI design + +The installer is required to run in Wayland but also perform privileged +operations. This necessitated splitting the installer into the following +pieces: + +- A user interface that can be run by a non-privileged user in their Wayland +session +- A back-end server that runs in the background to perform the privileged +operations on behalf of the user + +The user interface communicates with the back-end over DBUS. There are a simple +set of messages/signals to initiate the install process and provide updates to +the interface about the success/failure of each install stage. + +Both the user interface can only be run in install/live mode. The user +interface will start automatically when the computer is booted in install/live +mode, however, the user can close the interface and test out the system in +live mode to determine if it is compatible with their hardware, if they want to +actually perform an install, etc. If the user decides to install the system, +they can simply re-open the user interface while still in live mode. + + diff --git a/citadel-installer-ui/data/citadel_password_page.ui b/citadel-installer-ui/data/citadel_password_page.ui new file mode 100644 index 0000000..a856911 --- /dev/null +++ b/citadel-installer-ui/data/citadel_password_page.ui @@ -0,0 +1,126 @@ + + + + + + True + False + center + vertical + + + True + False + 24 + 40 + vertical + + + True + False + 24 + 96 + dialog-password-symbolic + + + False + True + 0 + + + + + True + False + Set Citadel User Password + + + False + True + 1 + + + + + False + True + 0 + + + + + True + False + 40 + 6 + 12 + + + True + False + end + Password: + right + + + 0 + 0 + + + + + True + False + end + Confirm Password: + right + + + 0 + 1 + + + + + True + True + False + + + 1 + 0 + + + + + True + True + False + + + 1 + 1 + + + + + True + False + + + 1 + 2 + + + + + + + + False + True + 1 + + + + diff --git a/citadel-installer-ui/data/confirm_install_page.ui b/citadel-installer-ui/data/confirm_install_page.ui new file mode 100644 index 0000000..7be7bc7 --- /dev/null +++ b/citadel-installer-ui/data/confirm_install_page.ui @@ -0,0 +1,101 @@ + + + + + + True + False + center + vertical + + + True + False + 24 + 40 + vertical + + + True + False + 96 + 24 + system-os-installer + 6 + + + False + True + 0 + + + + + True + False + Install Citadel + + + False + True + 1 + + + + + False + True + 0 + + + + + True + False + vertical + + + True + False + You are about to install Citadel to the following destination: + + + False + True + 0 + + + + + True + False + + + False + True + 1 + + + + + True + False + Press the <b>Apply</b> button to continue with the installation. + True + 20 + + + False + True + 2 + + + + + False + True + 1 + + + + diff --git a/citadel-installer-ui/data/install_destination_page.ui b/citadel-installer-ui/data/install_destination_page.ui new file mode 100644 index 0000000..abbd86d --- /dev/null +++ b/citadel-installer-ui/data/install_destination_page.ui @@ -0,0 +1,80 @@ + + + + + + True + False + center + vertical + + + True + False + 24 + 40 + vertical + + + install_destination_header_icon + True + False + 96 + 24 + drive-harddisk-symbolic + 6 + + + False + True + 0 + + + + + install_destination_header_label + True + False + Choose an installation destination + + + False + True + 1 + + + + + False + True + 0 + + + + + True + False + 40 + vertical + + + 600 + True + False + 18 + + + True + True + 0 + + + + + False + True + 1 + + + + diff --git a/citadel-installer-ui/data/install_page.ui b/citadel-installer-ui/data/install_page.ui new file mode 100644 index 0000000..97380af --- /dev/null +++ b/citadel-installer-ui/data/install_page.ui @@ -0,0 +1,88 @@ + + + + + + + + + + True + False + center + 24 + 40 + vertical + + + True + False + vertical + 24 + 96 + + + True + 24 + False + Installing Citadel + + + False + True + 0 + + + + + False + True + 0 + + + + + 200 + True + False + center + 40 + + + False + True + 1 + + + + + True + True + center + never + in + 200 + 200 + + + 600 + True + True + False + word-char + 10 + False + install_textbuffer + False + True + + + + + False + True + 2 + + + + diff --git a/citadel-installer-ui/data/luks_password_page.ui b/citadel-installer-ui/data/luks_password_page.ui new file mode 100644 index 0000000..ede0d10 --- /dev/null +++ b/citadel-installer-ui/data/luks_password_page.ui @@ -0,0 +1,126 @@ + + + + + + True + False + center + vertical + + + True + False + 24 + 40 + vertical + + + True + False + 24 + 96 + dialog-password-symbolic + + + False + True + 0 + + + + + True + False + Set a disk encryption password + + + False + True + 1 + + + + + False + True + 0 + + + + + True + False + 40 + 6 + 12 + + + True + False + end + Password: + right + + + 0 + 0 + + + + + True + False + end + Confirm Password: + right + + + 0 + 1 + + + + + True + True + False + + + 1 + 0 + + + + + True + True + False + + + 1 + 1 + + + + + True + False + + + 1 + 2 + + + + + + + + False + True + 1 + + + + diff --git a/citadel-installer-ui/data/style.css b/citadel-installer-ui/data/style.css new file mode 100644 index 0000000..2aa5507 --- /dev/null +++ b/citadel-installer-ui/data/style.css @@ -0,0 +1,22 @@ +button.default:not(disabled) { + background: #027b40; + color: #D1D7d7; +} + +button.default:disabled { + background: #D1D7d7; + color: #929595; +} + +button { + background: #D1D7d7; +} + +headerbar { + background: #3C4141; +} + +text { + background: #4C575C; + color: #D1D7D7; +} \ No newline at end of file diff --git a/citadel-installer-ui/data/welcome_page.ui b/citadel-installer-ui/data/welcome_page.ui new file mode 100644 index 0000000..ca3ad92 --- /dev/null +++ b/citadel-installer-ui/data/welcome_page.ui @@ -0,0 +1,58 @@ + + + + + + welcome_page + True + False + center + vertical + + + True + False + center + 120 + 40 + vertical + + + True + False + Welcome to the Citadel installer. + + + False + True + 0 + + + + + False + True + 0 + + + + + True + False + 40 + To install Citadel on your computer, press <b>Next</b>. + +To continue trying Citadel without installing it, press <b>Cancel</b>. + +If you decide to install Citadel after trying it out, just re-open this application. + + True + + + False + True + 1 + + + + diff --git a/citadel-installer-ui/src/builder.rs b/citadel-installer-ui/src/builder.rs new file mode 100644 index 0000000..0c2e31b --- /dev/null +++ b/citadel-installer-ui/src/builder.rs @@ -0,0 +1,48 @@ + +use gtk::prelude::*; +use crate::{Error, Result}; + +pub struct Builder { + builder: gtk::Builder, +} + +impl Builder { + pub fn new(source: &str) -> Self { + let builder = gtk::Builder::from_string(source); + Builder { builder } + } + + fn ok_or_err(type_name: &str, name: &str, object: Option) -> Result { + object.ok_or(Error::Builder(format!("failed to load {} {}", type_name, name))) + } + + pub fn get_entry(&self, name: &str) -> Result { + Self::ok_or_err("GtkEntry", name, self.builder.get_object(name)) + } + + pub fn get_box(&self, name: &str) -> Result { + Self::ok_or_err("GtkBox", name, self.builder.get_object(name)) + } + + + + pub fn get_label(&self, name: &str) -> Result { + Self::ok_or_err("GtkLabel", name, self.builder.get_object(name)) + } + + pub fn get_listbox(&self, name: &str) -> Result { + Self::ok_or_err("GtkListBox", name, self.builder.get_object(name)) + } + + pub fn get_progress_bar(&self, name: &str) -> Result { + Self::ok_or_err("GtkProgressBar", name, self.builder.get_object(name)) + } + + pub fn get_textview(&self, name: &str) -> Result { + Self::ok_or_err("GtkTextView", name, self.builder.get_object(name)) + } + + pub fn get_scrolled_window(&self, name: &str) -> Result { + Self::ok_or_err("GtkScrolledWindow", name, self.builder.get_object(name)) + } +} diff --git a/citadel-installer-ui/src/dbus_client.rs b/citadel-installer-ui/src/dbus_client.rs new file mode 100644 index 0000000..d475e86 --- /dev/null +++ b/citadel-installer-ui/src/dbus_client.rs @@ -0,0 +1,207 @@ +use dbus::arg; + +#[derive(Debug)] +pub struct ComSubgraphInstallerManagerInstallCompleted { +} + +impl arg::AppendAll for ComSubgraphInstallerManagerInstallCompleted { + fn append(&self, _: &mut arg::IterAppend) { + } +} + +impl arg::ReadAll for ComSubgraphInstallerManagerInstallCompleted { + fn read(_i: &mut arg::Iter) -> Result { + Ok(ComSubgraphInstallerManagerInstallCompleted {}) + } +} + +impl dbus::message::SignalArgs for ComSubgraphInstallerManagerInstallCompleted { + const NAME: &'static str = "InstallCompleted"; + const INTERFACE: &'static str = "com.subgraph.installer.Manager"; +} + +#[derive(Debug)] +pub struct ComSubgraphInstallerManagerRunInstallStarted { + pub text: String, +} + +impl arg::AppendAll for ComSubgraphInstallerManagerRunInstallStarted { + fn append(&self, _: &mut arg::IterAppend) { + } +} + +impl arg::ReadAll for ComSubgraphInstallerManagerRunInstallStarted { + fn read(i: &mut arg::Iter) -> Result { + Ok(ComSubgraphInstallerManagerRunInstallStarted { + text: i.read()?, + }) + } +} + +impl dbus::message::SignalArgs for ComSubgraphInstallerManagerRunInstallStarted { + const NAME: &'static str = "RunInstallStarted"; + const INTERFACE: &'static str = "com.subgraph.installer.Manager"; +} + +#[derive(Debug)] +pub struct ComSubgraphInstallerManagerDiskPartitioned { + pub text: String, +} + +impl arg::AppendAll for ComSubgraphInstallerManagerDiskPartitioned { + fn append(&self, _: &mut arg::IterAppend) { + } +} + +impl arg::ReadAll for ComSubgraphInstallerManagerDiskPartitioned { + fn read(i: &mut arg::Iter) -> Result { + Ok(ComSubgraphInstallerManagerDiskPartitioned { + text: i.read()? + //sender, + }) + } +} + +impl dbus::message::SignalArgs for ComSubgraphInstallerManagerDiskPartitioned { + const NAME: &'static str = "DiskPartitioned"; + const INTERFACE: &'static str = "com.subgraph.installer.Manager"; +} + +#[derive(Debug)] +pub struct ComSubgraphInstallerManagerLvmSetup { + pub text: String, +} + +impl arg::AppendAll for ComSubgraphInstallerManagerLvmSetup { + fn append(&self, _: &mut arg::IterAppend) { + } +} + +impl arg::ReadAll for ComSubgraphInstallerManagerLvmSetup { + fn read(i: &mut arg::Iter) -> Result { + Ok(ComSubgraphInstallerManagerLvmSetup { + text: i.read()? + }) + } +} + +impl dbus::message::SignalArgs for ComSubgraphInstallerManagerLvmSetup { + const NAME: &'static str = "LvmSetup"; + const INTERFACE: &'static str = "com.subgraph.installer.Manager"; +} + +#[derive(Debug)] +pub struct ComSubgraphInstallerManagerLuksSetup { + pub text: String, +} + +impl arg::AppendAll for ComSubgraphInstallerManagerLuksSetup { + fn append(&self, _: &mut arg::IterAppend) { + } +} + +impl arg::ReadAll for ComSubgraphInstallerManagerLuksSetup { + fn read(i: &mut arg::Iter) -> Result { + Ok(ComSubgraphInstallerManagerLuksSetup { + text: i.read()? + }) + } +} + +impl dbus::message::SignalArgs for ComSubgraphInstallerManagerLuksSetup { + const NAME: &'static str = "LuksSetup"; + const INTERFACE: &'static str = "com.subgraph.installer.Manager"; +} + +#[derive(Debug)] +pub struct ComSubgraphInstallerManagerBootSetup { + pub text: String, +} + +impl arg::AppendAll for ComSubgraphInstallerManagerBootSetup { + fn append(&self, _: &mut arg::IterAppend) { + } +} + +impl arg::ReadAll for ComSubgraphInstallerManagerBootSetup { + fn read(i: &mut arg::Iter) -> Result { + Ok(ComSubgraphInstallerManagerBootSetup { + text: i.read()? + }) + } +} + +impl dbus::message::SignalArgs for ComSubgraphInstallerManagerBootSetup { + const NAME: &'static str = "BootSetup"; + const INTERFACE: &'static str = "com.subgraph.installer.Manager"; +} + +#[derive(Debug)] +pub struct ComSubgraphInstallerManagerStorageCreated { + pub text: String, +} + +impl arg::AppendAll for ComSubgraphInstallerManagerStorageCreated { + fn append(&self, _: &mut arg::IterAppend) { + } +} + +impl arg::ReadAll for ComSubgraphInstallerManagerStorageCreated { + fn read(i: &mut arg::Iter) -> Result { + Ok(ComSubgraphInstallerManagerStorageCreated { + //sender, + text: i.read()? + }) + } +} + +impl dbus::message::SignalArgs for ComSubgraphInstallerManagerStorageCreated { + const NAME: &'static str = "StorageCreated"; + const INTERFACE: &'static str = "com.subgraph.installer.Manager"; +} + +#[derive(Debug)] +pub struct ComSubgraphInstallerManagerRootfsInstalled { + pub text: String, +} + +impl arg::AppendAll for ComSubgraphInstallerManagerRootfsInstalled { + fn append(&self, _: &mut arg::IterAppend) { + } +} + +impl arg::ReadAll for ComSubgraphInstallerManagerRootfsInstalled { + fn read(i: &mut arg::Iter) -> Result { + Ok(ComSubgraphInstallerManagerRootfsInstalled { + text: i.read()? + }) + } +} + +impl dbus::message::SignalArgs for ComSubgraphInstallerManagerRootfsInstalled { + const NAME: &'static str = "RootfsInstalled"; + const INTERFACE: &'static str = "com.subgraph.installer.Manager"; +} + +#[derive(Debug)] +pub struct ComSubgraphInstallerManagerInstallFailed { + pub text: String, +} + +impl arg::AppendAll for ComSubgraphInstallerManagerInstallFailed { + fn append(&self, _: &mut arg::IterAppend) { + } +} + +impl arg::ReadAll for ComSubgraphInstallerManagerInstallFailed { + fn read(i: &mut arg::Iter) -> Result { + Ok(ComSubgraphInstallerManagerInstallFailed { + text: i.read()?, + }) + } +} + +impl dbus::message::SignalArgs for ComSubgraphInstallerManagerInstallFailed { + const NAME: &'static str = "InstallFailed"; + const INTERFACE: &'static str = "com.subgraph.installer.Manager"; +} diff --git a/citadel-installer-ui/src/error.rs b/citadel-installer-ui/src/error.rs new file mode 100644 index 0000000..d9a09de --- /dev/null +++ b/citadel-installer-ui/src/error.rs @@ -0,0 +1,11 @@ + +use std::result; + +use dbus; +pub type Result = result::Result; + +#[derive(Debug)] +pub enum Error { + Dbus(dbus::Error), + Builder(String), +} diff --git a/citadel-installer-ui/src/main.rs b/citadel-installer-ui/src/main.rs new file mode 100644 index 0000000..512cbb2 --- /dev/null +++ b/citadel-installer-ui/src/main.rs @@ -0,0 +1,43 @@ +#![allow(deprecated)] +#[macro_use] extern crate glib; +use gtk::prelude::*; +use gio::prelude::*; +use std::env::args; +mod ui; +mod builder; +mod error; +mod rowdata; +mod dbus_client; +use libcitadel::CommandLine; +use ui::Ui; + +pub use error::{Result,Error}; + +fn main() { + let application = + gtk::Application::new(Some("com.subgraph.citadel-installer"), Default::default()) + .expect("Initialization failed..."); + + application.connect_activate(|app| { + if !(CommandLine::live_mode() || CommandLine::install_mode()) { + let dialog = gtk::MessageDialog::new( + None::<>k::Window>, + gtk::DialogFlags::empty(), + gtk::MessageType::Error, + gtk::ButtonsType::Cancel, + "Citadel Installer can only be run during install or live mode"); + dialog.run(); + } else { + match Ui::build(app) { + Ok(ui) => { + ui.assistant.show_all(); + ui.start(); + }, + Err(err) => { + println!("Could not start application: {:?}", err); + } + } + } + }); + application.run(&args().collect::>()); +} diff --git a/citadel-installer-ui/src/rowdata.rs b/citadel-installer-ui/src/rowdata.rs new file mode 100644 index 0000000..4ecbcaa --- /dev/null +++ b/citadel-installer-ui/src/rowdata.rs @@ -0,0 +1,156 @@ + +use gio::prelude::*; +use std::fmt; + +pub mod row_data { + + use super::*; + + use glib::subclass; + use glib::subclass::prelude::*; + use glib::translate::*; + + mod imp { + use super::*; + use std::cell::RefCell; + + pub struct RowData { + model: RefCell>, + path: RefCell>, + size: RefCell>, + removable: RefCell, + } + + static PROPERTIES: [subclass::Property; 4] = [ + subclass::Property("model", |name| { + glib::ParamSpec::string( + name, + "Model", + "Model", + None, // Default value + glib::ParamFlags::READWRITE, + ) + }), + subclass::Property("path", |name| { + glib::ParamSpec::string( + name, + "Path", + "Path", + None, // Default value + glib::ParamFlags::READWRITE, + ) + }), + subclass::Property("size", |name| { + glib::ParamSpec::string( + name, + "Size", + "Size", + None, // Default value + glib::ParamFlags::READWRITE, + ) + }), + subclass::Property("removable", |name| { + glib::ParamSpec::boolean( + name, + "Removable", + "Removable", + false, // Default value + glib::ParamFlags::READWRITE, + ) + }), + ]; + + impl ObjectSubclass for RowData { + const NAME: &'static str = "RowData"; + type ParentType = glib::Object; + type Instance = subclass::simple::InstanceStruct; + type Class = subclass::simple::ClassStruct; + + glib_object_subclass!(); + + fn class_init(klass: &mut Self::Class) { + klass.install_properties(&PROPERTIES); + } + + fn new() -> Self { + Self { + model: RefCell::new(None), + path: RefCell::new(None), + size: RefCell::new(None), + removable: RefCell::new(false), + } + } + } + + impl ObjectImpl for RowData { + glib_object_impl!(); + + fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) { + let prop = &PROPERTIES[id]; + + match *prop { + subclass::Property("model", ..) => { + let model = value + .get() + .expect("type conformity checked by `Object::set_property`"); + self.model.replace(model); + } + subclass::Property("path", ..) => { + let path = value + .get() + .expect("type conformity checked by `Object::set_property`"); + self.path.replace(path); + } + subclass::Property("size", ..) => { + let size = value + .get() + .expect("type conformity checked by `Object::set_property`"); + self.size.replace(size); + } + subclass::Property("removable", ..) => { + let removable = value + .get_some() + .expect("type conformity checked by `Object::set_property`"); + self.removable.replace(removable); + } + _ => unimplemented!(), + } + } + + fn get_property(&self, _obj: &glib::Object, id: usize) -> Result { + let prop = &PROPERTIES[id]; + + match *prop { + subclass::Property("model", ..) => Ok(self.model.borrow().to_value()), + subclass::Property("path", ..) => Ok(self.path.borrow().to_value()), + subclass::Property("size", ..) => Ok(self.size.borrow().to_value()), + subclass::Property("removable", ..) => Ok(self.removable.borrow().to_value()), + _ => unimplemented!(), + } + } + } + } + + glib_wrapper! { + pub struct RowData(Object, subclass::simple::ClassStruct, RowDataClass>); + + match fn { + get_type => || imp::RowData::get_type().to_glib(), + } + } + + impl RowData { + pub fn new(model: &str, path: &str, size: &str, removable: bool) -> RowData { + glib::Object::new(Self::static_type(), &[("model", &model), ("path", &path), ("size", &size), ("removable", &removable)]) + .expect("Failed to create row data") + .downcast() + .expect("Created row data is of wrong type") + } + } + + impl fmt::Display for RowData { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self.1) + } + } +} diff --git a/citadel-installer-ui/src/ui.rs b/citadel-installer-ui/src/ui.rs new file mode 100644 index 0000000..3b36e5c --- /dev/null +++ b/citadel-installer-ui/src/ui.rs @@ -0,0 +1,420 @@ +use gtk::prelude::*; + +use gio::prelude::*; +use dbus::Message; +use std::time::Duration; +use std::thread; +use std::collections::HashMap; +use dbus::blocking::{Connection, Proxy}; +use crate::builder::*; +use crate::rowdata::row_data::RowData; +use crate::{Result, Error}; +use crate::dbus_client::*; + +const STYLE: &str = include_str!("../data/style.css"); +const WELCOME_UI: &str = include_str!("../data/welcome_page.ui"); +const CITADEL_PASSWORD_UI: &str = include_str!("../data/citadel_password_page.ui"); +const LUKS_PASSWORD_UI: &str = include_str!("../data/luks_password_page.ui"); +const INSTALL_DESTINATION_UI: &str = include_str!("../data/install_destination_page.ui"); +const CONFIRM_INSTALL_UI: &str = include_str!("../data/confirm_install_page.ui"); +const INSTALL_UI: &str = include_str!("../data/install_page.ui"); +pub enum Msg { + InstallStarted, + LvmSetup(String), + LuksSetup(String), + BootSetup(String), + StorageCreated(String), + RootfsInstalled(String), + InstallCompleted, + InstallFailed(String) +} +#[derive(Clone)] +pub struct Ui { + pub assistant: gtk::Assistant, + pub citadel_password_page: gtk::Box, + pub citadel_password_entry: gtk::Entry, + pub citadel_password_confirm_entry: gtk::Entry, + pub citadel_password_status_label: gtk::Label, + pub luks_password_page: gtk::Box, + pub luks_password_entry: gtk::Entry, + pub luks_password_confirm_entry: gtk::Entry, + pub luks_password_status_label: gtk::Label, + pub disks_listbox: gtk::ListBox, + pub disks_model: gio::ListStore, + pub confirm_install_label: gtk::Label, + pub install_page: gtk::Box, + pub install_progress: gtk::ProgressBar, + pub install_scrolled_window: gtk::ScrolledWindow, + pub install_textview: gtk::TextView, + pub sender: glib::Sender +} + +impl Ui { + pub fn build(application: >k::Application) -> Result { + let disks = Self::get_disks()?; + let assistant = gtk::Assistant::new(); + assistant.set_default_size(800, 600); + assistant.set_position(gtk::WindowPosition::CenterAlways); + + assistant.set_application(Some(application)); + assistant.connect_delete_event(clone!(@strong application => move |_, _| { + application.quit(); + gtk::Inhibit(false) + })); + assistant.connect_cancel(clone!(@strong application => move |_| { + application.quit(); + })); + let welcome_builder = Builder::new(WELCOME_UI); + let welcome_page: gtk::Box = welcome_builder.get_box("welcome_page")?; + let citadel_password_builder = Builder::new(CITADEL_PASSWORD_UI); + let citadel_password_page: gtk::Box = citadel_password_builder.get_box("citadel_password_page")?; + let citadel_password_entry: gtk::Entry = citadel_password_builder.get_entry("citadel_password_entry")?; + let citadel_password_confirm_entry: gtk::Entry = citadel_password_builder.get_entry("citadel_password_confirm_entry")?; + let citadel_password_status_label: gtk::Label = citadel_password_builder.get_label("citadel_password_status_label")?; + + let luks_password_builder = Builder::new(LUKS_PASSWORD_UI); + let luks_password_page: gtk::Box = luks_password_builder.get_box("luks_password_page")?; + let luks_password_entry: gtk::Entry = luks_password_builder.get_entry("luks_password_entry")?; + let luks_password_confirm_entry: gtk::Entry = luks_password_builder.get_entry("luks_password_confirm_entry")?; + let luks_password_status_label: gtk::Label = luks_password_builder.get_label("luks_password_status_label")?; + + let install_destination_builder = Builder::new(INSTALL_DESTINATION_UI); + let install_destination_page: gtk::Box = install_destination_builder.get_box("install_destination_page")?; + let disks_listbox = install_destination_builder.get_listbox("install_destination_listbox")?; + + let confirm_install_builder = Builder::new(CONFIRM_INSTALL_UI); + let confirm_install_page: gtk::Box = confirm_install_builder.get_box("confirm_install_page")?; + let confirm_install_label: gtk::Label = confirm_install_builder.get_label("confirm_install_label_3")?; + let disks_model = gio::ListStore::new(RowData::static_type()); + disks_listbox.bind_model(Some(&disks_model), move |item| { + let row = gtk::ListBoxRow::new(); + let item = item.downcast_ref::().expect("Row data is of wrong type"); + let hbox = gtk::Box::new(gtk::Orientation::Horizontal, 5); + hbox.set_homogeneous(true); + let removable = item.get_property("removable").unwrap().get().unwrap().unwrap(); + let icon_name = Self::get_disk_icon(removable); + let disk_icon = gtk::Image::from_icon_name(Some(&icon_name), gtk::IconSize::LargeToolbar); + disk_icon.set_halign(gtk::Align::Start); + let model_label = gtk::Label::new(None); + model_label.set_halign(gtk::Align::Start); + model_label.set_justify(gtk::Justification::Left); + item.bind_property("model", &model_label, "label") + .flags(glib::BindingFlags::DEFAULT | glib::BindingFlags::SYNC_CREATE) + .build(); + let path_label = gtk::Label::new(None); + path_label.set_halign(gtk::Align::Start); + path_label.set_justify(gtk::Justification::Left); + item.bind_property("path", &path_label, "label") + .flags(glib::BindingFlags::DEFAULT | glib::BindingFlags::SYNC_CREATE) + .build(); + let size_label = gtk::Label::new(None); + size_label.set_halign(gtk::Align::Start); + size_label.set_justify(gtk::Justification::Left); + item.bind_property("size", &size_label, "label") + .flags(glib::BindingFlags::DEFAULT | glib::BindingFlags::SYNC_CREATE) + .build(); + hbox.pack_start(&disk_icon, true, true, 0); + hbox.pack_start(&path_label, true, true, 0); + hbox.pack_start(&model_label, true, true, 0); + hbox.pack_start(&size_label, true, true, 0); + row.add(&hbox); + row.show_all(); + row.upcast::() + }); + disks_listbox.connect_row_selected(clone!(@strong assistant, @strong install_destination_page => move |_, listbox_row | { + if let Some(_) = listbox_row { + assistant.set_page_complete(&install_destination_page, true); + } + })); + let install_builder = Builder::new(INSTALL_UI); + let install_page: gtk::Box = install_builder.get_box("install_page")?; + let install_progress: gtk::ProgressBar = install_builder.get_progress_bar("install_progress")?; + let install_scrolled_window: gtk::ScrolledWindow = install_builder.get_scrolled_window("install_scrolled_window")?; + let install_textview: gtk::TextView = install_builder.get_textview("install_textview")?; + assistant.append_page(&welcome_page); + assistant.set_page_type(&welcome_page, gtk::AssistantPageType::Intro); + assistant.set_page_complete(&welcome_page, true); + assistant.append_page(&citadel_password_page); + assistant.append_page(&luks_password_page); + assistant.append_page(&install_destination_page); + assistant.append_page(&confirm_install_page); + assistant.set_page_type(&confirm_install_page, gtk::AssistantPageType::Confirm); + assistant.set_page_complete(&confirm_install_page, true); + assistant.append_page(&install_page); + assistant.set_page_type(&install_page, gtk::AssistantPageType::Progress); + let disks_model_clone = disks_model.clone(); + let (sender, receiver) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); + let ui = Self { + assistant, + citadel_password_page, + citadel_password_entry, + citadel_password_confirm_entry, + citadel_password_status_label, + luks_password_page, + luks_password_entry, + luks_password_confirm_entry, + luks_password_status_label, + disks_listbox, + disks_model, + confirm_install_label, + install_page, + install_progress, + install_scrolled_window, + install_textview, + sender, + }; + receiver.attach(None,clone!(@strong ui, @strong application => move |msg| { + match msg { + Msg::InstallStarted => { + ui.install_progress.set_fraction(0.1428); + let buffer = ui.install_textview.get_buffer().unwrap(); + let mut iter = buffer.get_end_iter(); + let text = format!( + "+ Installing Citadel to {}. \nFor a full log, consult the systemd journal by running the following command:\n sudo journalctl -u citadel-installer-backend.service\n", + ui.get_install_destination()); + buffer.insert_markup(&mut iter, &text); + + }, + Msg::LuksSetup(text) => { + ui.install_progress.set_fraction(0.1428 * 2.0); + let buffer = ui.install_textview.get_buffer().unwrap(); + let mut iter = buffer.get_end_iter(); + buffer.insert(&mut iter, &text); + }, + Msg::LvmSetup(text) => { + ui.install_progress.set_fraction(0.1428 * 3.0); + let buffer = ui.install_textview.get_buffer().unwrap(); + let mut iter = buffer.get_end_iter(); + buffer.insert(&mut iter, &text); + }, + Msg::BootSetup(text) => { + ui.install_progress.set_fraction(0.1428 * 4.0); + let buffer = ui.install_textview.get_buffer().unwrap(); + let mut iter = buffer.get_end_iter(); + buffer.insert(&mut iter, &text); + }, + Msg::StorageCreated(text) => { + ui.install_progress.set_fraction(0.1428 * 5.0); + let buffer = ui.install_textview.get_buffer().unwrap(); + let mut iter = buffer.get_end_iter(); + buffer.insert(&mut iter, &text); + }, + Msg::RootfsInstalled(text) => { + ui.install_progress.set_fraction(0.1428 * 6.0); + let buffer = ui.install_textview.get_buffer().unwrap(); + let mut iter = buffer.get_end_iter(); + buffer.insert(&mut iter, &text); + }, + Msg::InstallCompleted => { + ui.install_progress.set_fraction(1.0); + let buffer = ui.install_textview.get_buffer().unwrap(); + let mut iter = buffer.get_end_iter(); + buffer.insert(&mut iter, "+ Completed the installation successfully\n"); + let quit_button = gtk::Button::with_label("Quit"); + quit_button.connect_clicked(clone!(@strong application => move |_| { + application.quit(); + })); + quit_button.set_sensitive(true); + ui.assistant.add_action_widget(&quit_button); + ui.assistant.show_all(); + }, + Msg::InstallFailed(error) => { + ui.install_progress.set_fraction(100.0); + let buffer = ui.install_textview.get_buffer().unwrap(); + let mut iter = buffer.get_end_iter(); + let text = format!("+ Install failed with error:\n{}\n", error); + buffer.insert_markup(&mut iter, &text); + let quit_button = gtk::Button::with_label("Quit"); + quit_button.connect_clicked(clone!(@strong application => move |_| { + application.quit(); + })); + quit_button.set_sensitive(true); + ui.assistant.add_action_widget(&quit_button); + ui.assistant.show_all(); + } + } + glib::Continue(true) + })); + ui.setup_style(); + ui.setup_signals(); + for disk in disks { + disks_model_clone.append(&disk); + } + Ok(ui) + } + + fn get_disks() -> Result> { + let mut disks = vec![]; + let conn = Connection::new_system().unwrap(); + let proxy = conn.with_proxy("com.subgraph.installer", + "/com/subgraph/installer", Duration::from_millis(5000)); + let (devices,): (HashMap>,) = proxy.method_call("com.subgraph.installer.Manager", "GetDisks", ()).map_err(Error::Dbus)?; + for device in devices { + let disk = RowData::new( + &device.1[0].clone(), + &device.0, + &device.1[1].clone(), + device.1[2].parse().unwrap()); + disks.push(disk); + } + Ok(disks) + } + + fn get_disk_icon(removable: bool) -> String { + if removable { + return "drive-harddisk-usb-symbolic".to_string(); + } + "drive-harddisk-system-symbolic".to_string() + } + + pub fn setup_entry_signals(&self, page: >k::Box, first_entry: >k::Entry, second_entry: >k::Entry, status_label: >k::Label) { + let ui = self.clone(); + let assistant = ui.assistant.clone(); + first_entry.connect_changed(clone!(@weak assistant, @weak page, @weak second_entry, @weak status_label => move |entry| { + let password = entry.get_text(); + let confirm = second_entry.get_text(); + if password != "" && confirm != "" { + let matches = password == confirm; + if !matches { + status_label.set_text("Passwords do not match"); + } else { + status_label.set_text(""); + } + assistant.set_page_complete(&page, matches); + } + })); + first_entry.connect_activate(clone!(@weak second_entry => move |_| { + second_entry.grab_focus(); + })); + } + + pub fn setup_prepare_signal(&self) { + let ui = self.clone(); + ui.assistant.connect_prepare(clone!(@strong ui => move |assistant, page| { + let page_type = assistant.get_page_type(page); + if page_type == gtk::AssistantPageType::Confirm { + let path = ui.get_install_destination(); + let text = format!("{}", path); + ui.confirm_install_label.set_markup(&text); + } + })); + } + + pub fn setup_apply_signal(&self) { + let ui = self.clone(); + ui.assistant.connect_apply(clone!(@strong ui => move |_| { + let citadel_password = ui.get_citadel_password(); + let luks_password = ui.get_luks_password(); + let destination = ui.get_install_destination(); + let conn = Connection::new_system().unwrap(); + let proxy = conn.with_proxy("com.subgraph.installer", + "/com/subgraph/installer", Duration::from_millis(5000)); + let (_,): (bool,) = proxy.method_call("com.subgraph.installer.Manager", + "RunInstall", (destination, citadel_password, luks_password)).unwrap(); + let _= ui.sender.send(Msg::InstallStarted); + })); + } + + pub fn setup_autoscroll_signal(&self) { + let ui = self.clone(); + let scrolled_window = ui.install_scrolled_window; + ui.install_textview.connect_size_allocate(clone!(@weak scrolled_window => move |_, _| { + let adjustment = scrolled_window.get_vadjustment().unwrap(); + adjustment.set_value(adjustment.get_upper() - adjustment.get_page_size()); + })); + } + + pub fn setup_signals(&self) { + let ui = self.clone(); + self.setup_entry_signals(&ui.citadel_password_page, &ui.citadel_password_entry, + &ui.citadel_password_confirm_entry, &ui.citadel_password_status_label); + self.setup_entry_signals(&ui.citadel_password_page, &ui.citadel_password_confirm_entry, + &ui.citadel_password_entry, &ui.citadel_password_status_label); + self.setup_entry_signals(&ui.luks_password_page, &ui.luks_password_entry, + &ui.luks_password_confirm_entry, &ui.luks_password_status_label); + self.setup_entry_signals(&ui.luks_password_page, &ui.luks_password_confirm_entry, + &ui.luks_password_entry, &ui.luks_password_status_label); + self.setup_prepare_signal(); + self.setup_apply_signal(); + self.setup_autoscroll_signal(); + } + + fn setup_style(&self) { + let css = gtk::CssProvider::new(); + + if let Err(err) = css.load_from_data(STYLE.as_bytes()) { + println!("Error parsing CSS style: {}", err); + return; + } + if let Some(screen) = gdk::Screen::get_default() { + gtk::StyleContext::add_provider_for_screen(&screen, &css, gtk::STYLE_PROVIDER_PRIORITY_USER); + } + } + + pub fn get_citadel_password(&self) -> String { + let ui = self.clone(); + let password = ui.citadel_password_entry.get_text(); + password.to_string() + } + + pub fn get_luks_password(&self) -> String { + let ui = self.clone(); + let password = ui.luks_password_entry.get_text(); + password.to_string() + } + + pub fn get_install_destination(&self) -> String { + let ui = self.clone(); + let model = ui.disks_model; + if let Some(row) = ui.disks_listbox.get_selected_row() { + let index = row.get_index() as u32; + let data = model.get_object(index).unwrap(); + let data = data.downcast_ref::().expect("Row data is of wrong type"); + // TODO: Fix unwrap + let path: String = data.get_property("path").unwrap().get().unwrap().unwrap(); + return path.to_string(); + } + "".to_string() + } + fn setup_signal_matchers(&self, proxy: Proxy<&Connection>) { + let sender = self.sender.clone(); + let _ = proxy.match_signal(clone!(@strong sender => move |_: ComSubgraphInstallerManagerInstallCompleted, _: &Connection, _: &Message| { + let _ = sender.send(Msg::InstallCompleted); + true + })); + let _ = proxy.match_signal(clone!(@strong sender => move |h: ComSubgraphInstallerManagerLvmSetup, _: &Connection, _: &Message| { + let _ = sender.send(Msg::LvmSetup(h.text)); + true + })); + let _ = proxy.match_signal(clone!(@strong sender => move |h: ComSubgraphInstallerManagerLuksSetup, _: &Connection, _: &Message| { + let _ = sender.send(Msg::LuksSetup(h.text)); + true + })); + let _ = proxy.match_signal(clone!(@strong sender => move |h: ComSubgraphInstallerManagerBootSetup, _: &Connection, _: &Message| { + let _ = sender.send(Msg::BootSetup(h.text)); + true + })); + let _ = proxy.match_signal(clone!(@strong sender => move |h: ComSubgraphInstallerManagerStorageCreated, _: &Connection, _: &Message| { + let _ = sender.send(Msg::StorageCreated(h.text)); + true + })); + let _ = proxy.match_signal(clone!(@strong sender => move |h: ComSubgraphInstallerManagerRootfsInstalled, _: &Connection, _: &Message| { + let _ = sender.send(Msg::RootfsInstalled(h.text)); + true + })); + let _ = proxy.match_signal(clone!(@strong sender => move |h: ComSubgraphInstallerManagerInstallFailed, _: &Connection, _: &Message| { + let _ = sender.send(Msg::InstallFailed(h.text)); + true + })); + } + + pub fn start(&self) { + let c = Connection::new_system().unwrap(); + let proxy = c.with_proxy("com.subgraph.installer", "/com/subgraph/installer", Duration::from_millis(5000)); + self.setup_signal_matchers(proxy); + thread::spawn(move || { + loop { + c.process(Duration::from_millis(1000)).unwrap(); } + }); + } +} diff --git a/citadel-tool/Cargo.toml b/citadel-tool/Cargo.toml index f145da2..71ed183 100644 --- a/citadel-tool/Cargo.toml +++ b/citadel-tool/Cargo.toml @@ -3,6 +3,8 @@ name = "citadel-tool" version = "0.1.0" authors = ["Bruce Leidl "] edition = "2018" +description = "citadel-tool" +homepage = "https://subgraph.com" [dependencies] libcitadel = { path = "../libcitadel" } @@ -14,4 +16,5 @@ serde = "1.0" toml = "0.5" hex = "0.4" byteorder = "1" - +dbus = "0.8.4" +pwhash = "0.3.1" diff --git a/citadel-tool/src/install/cli.rs b/citadel-tool/src/install/cli.rs index b032a62..27704dc 100644 --- a/citadel-tool/src/install/cli.rs +++ b/citadel-tool/src/install/cli.rs @@ -5,6 +5,9 @@ use super::disk::Disk; use rpassword; use crate::install::installer::Installer; +const CITADEL_PASSPHRASE_PROMPT: &str = "Enter a password for the Citadel user (or 'q' to quit)"; +const LUKS_PASSPHRASE_PROMPT: &str = "Enter a disk encryption passphrase (or 'q' to quit"; + pub fn run_cli_install() -> Result { let disk = match choose_disk()? { Some(disk) => disk, @@ -13,7 +16,12 @@ pub fn run_cli_install() -> Result { display_disk(&disk); - let passphrase = match read_passphrase().map_err(context!("error reading passphrase"))? { + let citadel_passphrase = match read_passphrase(CITADEL_PASSPHRASE_PROMPT).map_err(context!("error reading citadel user passphrase"))? { + Some(citadel_passphrase) => citadel_passphrase, + None => return Ok(false), + }; + + let passphrase = match read_passphrase(LUKS_PASSPHRASE_PROMPT).map_err(context!("error reading luks passphrase"))? { Some(passphrase) => passphrase, None => return Ok(false), }; @@ -21,7 +29,7 @@ pub fn run_cli_install() -> Result { if !confirm_install(&disk)? { return Ok(false); } - run_install(disk, passphrase)?; + run_install(disk, citadel_passphrase, passphrase)?; Ok(true) } @@ -29,7 +37,12 @@ pub fn run_cli_install_with>(target: P) -> Result { let disk = find_disk_by_path(target.as_ref())?; display_disk(&disk); - let passphrase = match read_passphrase().map_err(context!("error reading passphrase"))? { + let citadel_passphrase = match read_passphrase(CITADEL_PASSPHRASE_PROMPT).map_err(context!("error reading citadel user passphrase"))? { + Some(citadel_passphrase) => citadel_passphrase, + None => return Ok(false), + }; + + let passphrase = match read_passphrase(LUKS_PASSPHRASE_PROMPT).map_err(context!("error reading luks passphrase"))? { Some(passphrase) => passphrase, None => return Ok(false), }; @@ -38,12 +51,12 @@ pub fn run_cli_install_with>(target: P) -> Result { return Ok(false); } - run_install(disk, passphrase)?; + run_install(disk, citadel_passphrase, passphrase)?; Ok(true) } -fn run_install(disk: Disk, passphrase: String) -> Result<()> { - let mut install = Installer::new(disk.path(), &passphrase); +fn run_install(disk: Disk, citadel_passphrase: String, passphrase: String) -> Result<()> { + let mut install = Installer::new(disk.path(), &citadel_passphrase, &passphrase); install.set_install_syslinux(true); install.verify()?; install.run() @@ -108,9 +121,9 @@ fn read_line() -> Result { Ok(input) } -fn read_passphrase() -> io::Result> { +fn read_passphrase(prompt: &str) -> io::Result> { loop { - println!("Enter a disk encryption passphrase (or 'q' to quit)"); + println!("{}", prompt); println!(); let passphrase = rpassword::read_password_from_tty(Some(" Passphrase : "))?; if passphrase.is_empty() { diff --git a/citadel-tool/src/install/installer.rs b/citadel-tool/src/install/installer.rs index 547e155..101972e 100644 --- a/citadel-tool/src/install/installer.rs +++ b/citadel-tool/src/install/installer.rs @@ -5,6 +5,7 @@ use std::os::unix::process::CommandExt; use std::path::{Path, PathBuf}; use std::process::Command; use std::time::Instant; +use pwhash::sha512_crypt; use libcitadel::util; use libcitadel::RealmFS; @@ -121,20 +122,23 @@ pub struct Installer { install_syslinux: bool, storage_base: PathBuf, target_device: Option, + citadel_passphrase: Option, passphrase: Option, artifact_directory: String, logfile: Option>, } impl Installer { - pub fn new>(target_device: P, passphrase: &str) -> Installer { + pub fn new>(target_device: P, citadel_passphrase: &str, passphrase: &str) -> Installer { let target_device = Some(target_device.as_ref().to_owned()); + let citadel_passphrase = Some(citadel_passphrase.to_owned()); let passphrase = Some(passphrase.to_owned()); Installer { _type: InstallType::Install, install_syslinux: true, storage_base: PathBuf::from(INSTALL_MOUNT), target_device, + citadel_passphrase, passphrase, artifact_directory: DEFAULT_ARTIFACT_DIRECTORY.to_string(), logfile: None, @@ -147,6 +151,7 @@ impl Installer { install_syslinux: false, storage_base: PathBuf::from("/sysroot/storage"), target_device: None, + citadel_passphrase: None, passphrase: None, artifact_directory: DEFAULT_ARTIFACT_DIRECTORY.to_string(), logfile: None, @@ -161,6 +166,10 @@ impl Installer { self.target().to_str().unwrap() } + fn citadel_passphrase(&self) -> &str { + self.citadel_passphrase.as_ref().expect("No citadel passphrase") + } + fn passphrase(&self) -> &str { self.passphrase.as_ref().expect("No passphrase") } @@ -251,14 +260,14 @@ impl Installer { Ok(()) } - fn partition_disk(&self) -> Result<()> { + pub fn partition_disk(&self) -> Result<()> { self.header("Partitioning target disk")?; self.cmd_list(PARTITION_COMMANDS, &[ ("$TARGET", self.target_str()) ]) } - fn setup_luks(&self) -> Result<()> { + pub fn setup_luks(&self) -> Result<()> { self.header("Setting up LUKS disk encryption")?; util::create_dir(INSTALL_MOUNT)?; util::write_file(LUKS_PASSPHRASE_FILE, self.passphrase().as_bytes())?; @@ -274,12 +283,12 @@ impl Installer { util::remove_file(LUKS_PASSPHRASE_FILE) } - fn setup_lvm(&self) -> Result<()> { + pub fn setup_lvm(&self) -> Result<()> { self.header("Setting up LVM volumes")?; self.cmd_list(LVM_COMMANDS, &[]) } - fn setup_boot(&self) -> Result<()> { + pub fn setup_boot(&self) -> Result<()> { self.header("Setting up /boot partition")?; let boot_partition = self.target_partition(1); self.cmd(format!("/sbin/mkfs.vfat -F 32 {}", boot_partition))?; @@ -327,9 +336,11 @@ impl Installer { util::copy_file(dent.path(), dst.join(dent.file_name())) })?; + let kernel_version = self.kernel_version(); self.info("Writing syslinux.cfg")?; util::write_file(dst.join("syslinux.cfg"), - SYSLINUX_CONF.replace("$KERNEL_CMDLINE", KERNEL_CMDLINE))?; + SYSLINUX_CONF.replace("$KERNEL_CMDLINE", KERNEL_CMDLINE) + .replace("$KERNEL_VERSION", &kernel_version))?; self.cmd(format!("/sbin/extlinux --install {}", dst.display())) } @@ -343,7 +354,7 @@ impl Installer { } - fn create_storage(&self) -> Result<()> { + pub fn create_storage(&self) -> Result<()> { self.header("Setting up /storage partition")?; self.cmd_list(CREATE_STORAGE_COMMANDS, @@ -363,6 +374,7 @@ impl Installer { self.setup_realm_skel()?; self.setup_main_realm()?; self.setup_apt_cacher_realm()?; + self.setup_citadel_passphrase()?; self.info("Creating global realm config file")?; util::write_file(self.storage().join("realms/config"), self.global_realm_config())?; @@ -382,6 +394,7 @@ impl Installer { keyring.write(self.storage().join("keyring"), self.passphrase.as_ref().unwrap()) } + fn setup_base_realmfs(&self) -> Result<()> { let realmfs_dir = self.storage().join("realms/realmfs-images"); util::create_dir(&realmfs_dir)?; @@ -460,14 +473,37 @@ impl Installer { self.sparse_copy_artifact(&kernel_img, &resources) } - fn install_rootfs_partitions(&self) -> Result<()> { + fn setup_citadel_passphrase(&self) -> Result<()> { + if self._type == InstallType::LiveSetup { + self.info("Creating temporary citadel passphrase file for live mode")?; + let path = self.storage().join("citadel-state/passwd"); + if !path.exists() { + if let Ok(hash) = sha512_crypt::hash("citadel") { + let contents = format!("citadel:{}\n", hash); + util::create_dir(self.storage().join("citadel-state"))?; + util::write_file(self.storage().join("citadel-state/passwd"), contents)?; + } + } + } + else if self._type == InstallType::Install { + self.info("Creating citadel passphrase file")?; + if let Ok(hash) = sha512_crypt::hash(self.citadel_passphrase()) { + let contents = format!("citadel:{}\n", hash); + util::create_dir(self.storage().join("citadel-state"))?; + util::write_file(self.storage().join("citadel-state/passwd"), contents)?; + } + } + Ok(()) + } + + pub fn install_rootfs_partitions(&self) -> Result<()> { self.header("Installing rootfs partitions")?; let rootfs = self.artifact_path("citadel-rootfs.img"); self.cmd(format!("/usr/bin/citadel-image install-rootfs --skip-sha {}", rootfs.display()))?; self.cmd(format!("/usr/bin/citadel-image install-rootfs --skip-sha --no-prefer {}", rootfs.display())) } - fn finish_install(&self) -> Result<()> { + pub fn finish_install(&self) -> Result<()> { self.cmd_list(FINISH_COMMANDS, &[ ("$TARGET", self.target_str()) ]) diff --git a/citadel-tool/src/install_backend/dbus.rs b/citadel-tool/src/install_backend/dbus.rs new file mode 100644 index 0000000..6247d91 --- /dev/null +++ b/citadel-tool/src/install_backend/dbus.rs @@ -0,0 +1,278 @@ +use std::sync::Arc; +use std::collections::HashMap; +use std::time::Duration; +use std::sync::mpsc; +use std::sync::mpsc::{Sender}; + +use dbus::tree::{self, Factory, MTFn, MethodResult, Tree}; +use dbus::{Message}; +use dbus::blocking::LocalConnection; +use libcitadel::{Result}; +// Use local version of disk.rs since we added some methods +use crate::install_backend::disk::*; +use crate::install::installer::*; +use std::fmt; + +type MethodInfo<'a> = tree::MethodInfo<'a, MTFn, TData>; + + +const OBJECT_PATH: &str = "/com/subgraph/installer"; +const INTERFACE_NAME: &str = "com.subgraph.installer.Manager"; +const BUS_NAME: &str = "com.subgraph.installer"; + +pub enum Msg { + RunInstall(String, String, String), + LvmSetup(String), + LuksSetup(String), + BootSetup(String), + StorageCreated(String), + RootfsInstalled(String), + InstallCompleted, + InstallFailed(String) +} + +pub struct DbusServer { + connection: Arc, + //events: EventHandler, +} + +impl DbusServer { + + pub fn connect() -> Result { + let connection = LocalConnection::new_system() + .map_err(|e| format_err!("Failed to connect to DBUS system bus: {}", e))?; + let connection = Arc::new(connection); + //let events = EventHandler::new(connection.clone()); + let server = DbusServer { connection }; + Ok(server) + + } + + fn build_tree(&self, sender: mpsc::Sender) -> Tree, TData> { + let f = Factory::new_fn::(); + let data = TreeData::new(); + let interface = f.interface(INTERFACE_NAME, ()) + // Methods + .add_m(f.method("GetDisks", (), Self::do_get_disks) + .in_arg(("name", "a{sas}"))) + + .add_m(f.method("RunInstall", (),move |m| { + + let (device, citadel_passphrase, luks_passphrase): (String, String, String) = m.msg.read3()?; + println!("Device: {} Citadel Passphrase: {} Luks Passphrase: {}", device, citadel_passphrase, luks_passphrase); + let _ = sender.send(Msg::RunInstall(device, citadel_passphrase, luks_passphrase)); + Ok(vec![m.msg.method_return().append1(true)]) + }) + .in_arg(("device", "s")).in_arg(("citadel_passphrase", "s")).in_arg(("luks_passphrase", "s"))) + .add_s(f.signal("RunInstallStarted", ())) + .add_s(f.signal("InstallCompleted", ())) + .add_s(f.signal("CitadelPasswordSet", ())); + let obpath = f.object_path(OBJECT_PATH, ()) + .introspectable() + .add(interface); + + f.tree(data).add(obpath) + } + + + fn do_get_disks(m: &MethodInfo) -> MethodResult { + let list = m.tree.get_data().disks(); + Ok(vec![m.msg.method_return().append1(list)]) + } + + fn run_install(path: String, citadel_passphrase: String, luks_passphrase: String, sender: Sender) -> Result<()> { + let mut install = Installer::new(path, &citadel_passphrase, &luks_passphrase); + install.set_install_syslinux(true); + install.verify()?; + install.partition_disk()?; + install.setup_luks()?; + let _ = sender.send(Msg::LuksSetup("+ Setup LUKS disk encryption password successfully\n".to_string())); + install.setup_lvm()?; + let _ = sender.send(Msg::LvmSetup("+ Setup LVM volumes successfully\n".to_string())); + install.setup_boot()?; + let _ = sender.send(Msg::BootSetup("+ Setup /boot partition successfully\n".to_string())); + install.create_storage()?; + let _ = sender.send(Msg::StorageCreated("+ Setup /storage partition successfully\n".to_string())); + install.install_rootfs_partitions()?; + let _ = sender.send(Msg::RootfsInstalled("+ Installed rootfs partitions successfully\n".to_string())); + install.finish_install()?; + Ok(()) + } + + /*fn process_message(&self, _msg: Message) -> Result<()> { + // add handlers for expected signals here + Ok(()) + }*/ + + fn send_service_started(&self) { + let signal = Self::create_signal("ServiceStarted"); + if self.connection.channel().send(signal).is_err() { + warn!("Failed to send ServiceStarted signal"); + } + } + + fn send_install_completed(&self) { + let signal = Self::create_signal("InstallCompleted"); + if self.connection.channel().send(signal).is_err() { + warn!("Failed to send InstallCompleted signal"); + } + } + + fn send_lvm_setup(&self, text: String) { + let signal = Self::create_signal_with_text("LvmSetup", text); + if self.connection.channel().send(signal).is_err() { + warn!("Failed to send LvmSetup signal"); + } + } + + fn send_luks_setup(&self, text: String) { + let signal = Self::create_signal_with_text("LuksSetup", text); + if self.connection.channel().send(signal).is_err() { + warn!("Failed to send LuksSetup signal"); + } + } + + fn send_boot_setup(&self, text: String) { + let signal = Self::create_signal_with_text("BootSetup", text); + if self.connection.channel().send(signal).is_err() { + warn!("Failed to send BootSetup signal"); + } + } + + fn send_storage_created(&self, text: String) { + let signal = Self::create_signal_with_text("StorageCreated", text); + if self.connection.channel().send(signal).is_err() { + warn!("Failed to send StorageCreated signal"); + } + } + + fn send_rootfs_installed(&self, text: String) { + let signal = Self::create_signal_with_text("RootfsInstalled", text); + if self.connection.channel().send(signal).is_err() { + warn!("Failed to send StorageCreated signal"); + } + } + + fn send_install_failed(&self, error: String) { + let signal = Self::create_signal_with_text("InstallFailed", error); + if self.connection.channel().send(signal).is_err() { + warn!("Failed to send StorageCreated signal"); + } + } + + fn create_signal(name: &str) -> Message { + let path = dbus::Path::new(OBJECT_PATH).unwrap(); + let iface = dbus::strings::Interface::new(INTERFACE_NAME).unwrap(); + let member = dbus::strings::Member::new(name).unwrap(); + Message::signal(&path, &iface, &member) + } + + fn create_signal_with_text(name: &str, text: String) -> Message { + let path = dbus::Path::new(OBJECT_PATH).unwrap(); + let iface = dbus::strings::Interface::new(INTERFACE_NAME).unwrap(); + let member = dbus::strings::Member::new(name).unwrap(); + Message::signal(&path, &iface, &member).append1(text) + } + + pub fn start(&self) -> Result<()> { + let (sender, receiver) = mpsc::channel::(); + let sender_clone = sender.clone(); + let tree = self.build_tree(sender); + if let Err(_err) = self.connection.request_name(BUS_NAME, false, true, false) { + bail!("Failed to request name"); + } + + tree.start_receive(self.connection.as_ref()); + + self.send_service_started(); + loop { + self.connection + .process(Duration::from_millis(1000)) + .map_err(context!("Error handling dbus messages"))?; + + if let Ok(msg) = receiver.try_recv() { + match msg { + Msg::RunInstall(device, citadel_passphrase, luks_passphrase) => { + let install_sender = sender_clone.clone(); + // TODO: Implement more stages + match Self::run_install(device, citadel_passphrase, luks_passphrase, install_sender) { + Ok(_) => { + println!("Install completed"); + let _ = sender_clone.send(Msg::InstallCompleted); + }, + Err(err) => { + println!("Install error: {}", err); + let _ = sender_clone.send(Msg::InstallFailed(err.to_string())); + } + } + }, + Msg::LvmSetup(text) => { + self.send_lvm_setup(text); + }, + Msg::LuksSetup(text) => { + self.send_luks_setup(text); + }, + Msg::BootSetup(text) => { + self.send_boot_setup(text); + }, + Msg::StorageCreated(text) => { + self.send_storage_created(text); + }, + Msg::RootfsInstalled(text) => { + self.send_rootfs_installed(text); + }, + Msg::InstallCompleted => { + self.send_install_completed(); + }, + Msg::InstallFailed(text) => { + self.send_install_failed(text); + } + } + } + } + } + +} + +#[derive(Clone)] +struct TreeData { +} + +impl TreeData { + fn new() -> TreeData { + TreeData {} + } + + + fn disks(&self) -> HashMap> { + let disks = Disk::probe_all().unwrap(); + + let mut disk_map = HashMap::new(); + for d in disks { + let mut fields = vec![]; + fields.push(d.model().to_string()); + fields.push(d.size_str().to_string()); + fields.push(d.removable().to_string()); + disk_map.insert(d.path().to_string_lossy().to_string(), fields); + } + disk_map + } + +} +impl fmt::Debug for TreeData { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "") + } +} + +#[derive(Copy, Clone, Default, Debug)] +struct TData; + +impl tree::DataType for TData { + type Tree = TreeData; + type ObjectPath = (); + type Property = (); + type Interface = (); + type Method = (); + type Signal = (); +} diff --git a/citadel-tool/src/install_backend/disk.rs b/citadel-tool/src/install_backend/disk.rs new file mode 100644 index 0000000..8946163 --- /dev/null +++ b/citadel-tool/src/install_backend/disk.rs @@ -0,0 +1,80 @@ +use std::path::{Path,PathBuf}; +use std::fs; + +use libcitadel::{Result, util}; + + +#[derive(Debug, Clone)] +pub struct Disk { + path: PathBuf, + size: usize, + size_str: String, + model: String, + removable: bool, +} + +impl Disk { + pub fn probe_all() -> Result> { + let mut v = Vec::new(); + util::read_directory("/sys/block", |dent| { + let path = dent.path(); + if Disk::is_disk_device(&path) { + let disk = Disk::read_device(&path)?; + v.push(disk); + } + Ok(()) + })?; + + Ok(v) + } + + fn is_disk_device(device: &Path) -> bool { + device.join("device/model").exists() + } + + fn is_disk_removable(device: &Path) -> bool { + if let Ok(removable) = util::read_to_string(device.join("removable")) { + if removable.trim() == "1" { + return true + } + } + false + } + + fn read_device(device: &Path) -> Result { + let path = Path::new("/dev/").join(device.file_name().unwrap()); + + let size = fs::read_to_string(device.join("size")) + .map_err(context!("failed to read device size for {:?}", device))? + .trim() + .parse::() + .map_err(context!("error parsing device size for {:?}", device))?; + + let size_str = format!("{}G", size >> 21); + + let model = fs::read_to_string(device.join("device/model")) + .map_err(context!("failed to read device/model for {:?}", device))? + .trim() + .to_string(); + + let removable = Disk::is_disk_removable(device); + + Ok(Disk { path, size, size_str, model, removable }) + } + + pub fn path(&self) -> &Path { + &self.path + } + + pub fn size_str(&self) -> &str { + &self.size_str + } + + pub fn model(&self) -> &str { + &self.model + } + + pub fn removable(&self) -> &bool { + &self.removable + } +} diff --git a/citadel-tool/src/install_backend/mod.rs b/citadel-tool/src/install_backend/mod.rs new file mode 100644 index 0000000..50a4d8e --- /dev/null +++ b/citadel-tool/src/install_backend/mod.rs @@ -0,0 +1,24 @@ +use libcitadel::Result; +use std::process::exit; + +mod disk; +mod dbus; +use libcitadel::CommandLine; + +pub fn main() { + if CommandLine::live_mode() || CommandLine::install_mode() { + if let Err(e) = run_dbus_server() { + warn!("Error: {}", e); + } + } else { + println!("Citadel installer backend will only run in install or live mode"); + exit(1); + } +} + +fn run_dbus_server() -> Result<()> { + let server = dbus::DbusServer::connect()?; + server.start()?; + Ok(()) +} + diff --git a/citadel-tool/src/main.rs b/citadel-tool/src/main.rs index c0a7231..b07feda 100644 --- a/citadel-tool/src/main.rs +++ b/citadel-tool/src/main.rs @@ -11,6 +11,7 @@ use libcitadel::RealmManager; mod boot; mod image; mod install; +mod install_backend; mod mkimage; mod realmfs; mod sync; @@ -30,6 +31,8 @@ fn main() { boot::main(args); } else if exe == Path::new("/usr/libexec/citadel-install") { install::main(args); + } else if exe == Path::new("/usr/libexec/citadel-install-backend") { + install_backend::main(); } else if exe == Path::new("/usr/bin/citadel-image") { image::main(args); } else if exe == Path::new("/usr/bin/citadel-realmfs") { diff --git a/data/com.subgraph.installer.Manager.conf b/data/com.subgraph.installer.Manager.conf new file mode 100644 index 0000000..3b2ff13 --- /dev/null +++ b/data/com.subgraph.installer.Manager.conf @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + +