Implemented an installer user interface and backend

This commit is contained in:
David McKinney 2020-10-08 09:15:40 -04:00 committed by Bruce Leidl
parent 3d3b794b1d
commit ac46b45f05
25 changed files with 2749 additions and 18 deletions

1
citadel-installer-ui/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

750
citadel-installer-ui/Cargo.lock generated Normal file
View File

@ -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"

View File

@ -0,0 +1,15 @@
[package]
name = "citadel-installer-ui"
version = "0.1.0"
authors = ["David McKinney <mckinney@subgraph.com>"]
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"

View File

@ -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.

View File

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkBox" id="citadel_password_page">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="citadel_password_page_header">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">24</property>
<property name="margin_bottom">40</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkImage" id="citadel_password_header_icon">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">24</property>
<property name="pixel_size">96</property>
<property name="icon_name">dialog-password-symbolic</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="citadel_password_header_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Set Citadel User Password</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="citadel_password_grid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">40</property>
<property name="row_spacing">6</property>
<property name="column_spacing">12</property>
<child>
<object class="GtkLabel" id="citadel_password_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Password: </property>
<property name="justify">right</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="citadel_password_confirm_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Confirm Password:</property>
<property name="justify">right</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="citadel_password_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="visibility">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="citadel_password_confirm_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="visibility">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="citadel_password_status_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</interface>

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkBox" id="confirm_install_page">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="confirm_install_page_header">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">24</property>
<property name="margin_bottom">40</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkImage" id="confirm_install_page_header_icon">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="pixel_size">96</property>
<property name="margin_top">24</property>
<property name="icon_name">system-os-installer</property>
<property name="icon_size">6</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="confirm_install_page_header_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Install Citadel</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="confirm_install_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="confirm_install_label_1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">You are about to install Citadel to the following destination:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="confirm_install_label_3">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="confirm_install_label_2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Press the &lt;b&gt;Apply&lt;/b&gt; button to continue with the installation. </property>
<property name="use_markup">True</property>
<property name="margin_top">20</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</interface>

View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkBox" id="install_destination_page">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="install_destination_page_header">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">24</property>
<property name="margin_bottom">40</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkImage">
<property name="name">install_destination_header_icon</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="pixel_size">96</property>
<property name="margin_top">24</property>
<property name="icon_name">drive-harddisk-symbolic</property>
<property name="icon_size">6</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="name">install_destination_header_label</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Choose an installation destination</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox" id="install_destination_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">40</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkListBox" id="install_destination_listbox">
<property name="width_request">600</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">18</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</interface>

View File

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkTextBuffer" id="install_textbuffer">
<property name="text" translatable="yes">
</property>
</object>
<object class="GtkBox" id="install_page">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="margin_top">24</property>
<property name="margin_bottom">40</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="margin_top">24</property>
<property name="margin_bottom">96</property>
<child>
<object class="GtkLabel" id="install_header_label">
<property name="visible">True</property>
<property name="margin_top">24</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Installing Citadel</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkProgressBar" id="install_progress">
<property name="width_request">200</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="margin_bottom">40</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="install_scrolled_window">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="valign">center</property>
<property name="hscrollbar_policy">never</property>
<property name="shadow_type">in</property>
<property name="min_content_width">200</property>
<property name="min_content_height">200</property>
<child>
<object class="GtkTextView" id="install_textview">
<property name="width_request">600</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="wrap_mode">word-char</property>
<property name="indent">10</property>
<property name="cursor_visible">False</property>
<property name="buffer">install_textbuffer</property>
<property name="accepts_tab">False</property>
<property name="monospace">True</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</interface>

View File

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkBox" id="luks_password_page">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="luks_password_page_header">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">24</property>
<property name="margin_bottom">40</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkImage" id="luks_password_header_icon">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">24</property>
<property name="pixel_size">96</property>
<property name="icon_name">dialog-password-symbolic</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="luks_password_header_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Set a disk encryption password</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="luks_password_grid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">40</property>
<property name="row_spacing">6</property>
<property name="column_spacing">12</property>
<child>
<object class="GtkLabel" id="luks_password_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Password: </property>
<property name="justify">right</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="luks_password_confirm_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Confirm Password:</property>
<property name="justify">right</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="luks_password_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="visibility">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="luks_password_confirm_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="visibility">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="luks_password_status_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</interface>

View File

@ -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;
}

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkBox" id="welcome_page">
<property name="name">welcome_page</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="welcome_header">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="margin_top">120</property>
<property name="margin_bottom">40</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="welcome_header_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Welcome to the Citadel installer.</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="welcome_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">40</property>
<property name="label" translatable="yes">To install Citadel on your computer, press &lt;b&gt;Next&lt;/b&gt;.
To continue trying Citadel without installing it, press &lt;b&gt;Cancel&lt;/b&gt;.
If you decide to install Citadel after trying it out, just re-open this application.
</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</interface>

View File

@ -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<T>(type_name: &str, name: &str, object: Option<T>) -> Result<T> {
object.ok_or(Error::Builder(format!("failed to load {} {}", type_name, name)))
}
pub fn get_entry(&self, name: &str) -> Result<gtk::Entry> {
Self::ok_or_err("GtkEntry", name, self.builder.get_object(name))
}
pub fn get_box(&self, name: &str) -> Result<gtk::Box> {
Self::ok_or_err("GtkBox", name, self.builder.get_object(name))
}
pub fn get_label(&self, name: &str) -> Result<gtk::Label> {
Self::ok_or_err("GtkLabel", name, self.builder.get_object(name))
}
pub fn get_listbox(&self, name: &str) -> Result<gtk::ListBox> {
Self::ok_or_err("GtkListBox", name, self.builder.get_object(name))
}
pub fn get_progress_bar(&self, name: &str) -> Result<gtk::ProgressBar> {
Self::ok_or_err("GtkProgressBar", name, self.builder.get_object(name))
}
pub fn get_textview(&self, name: &str) -> Result<gtk::TextView> {
Self::ok_or_err("GtkTextView", name, self.builder.get_object(name))
}
pub fn get_scrolled_window(&self, name: &str) -> Result<gtk::ScrolledWindow> {
Self::ok_or_err("GtkScrolledWindow", name, self.builder.get_object(name))
}
}

View File

@ -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<Self, arg::TypeMismatchError> {
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<Self, arg::TypeMismatchError> {
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<Self, arg::TypeMismatchError> {
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<Self, arg::TypeMismatchError> {
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<Self, arg::TypeMismatchError> {
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<Self, arg::TypeMismatchError> {
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<Self, arg::TypeMismatchError> {
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<Self, arg::TypeMismatchError> {
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<Self, arg::TypeMismatchError> {
Ok(ComSubgraphInstallerManagerInstallFailed {
text: i.read()?,
})
}
}
impl dbus::message::SignalArgs for ComSubgraphInstallerManagerInstallFailed {
const NAME: &'static str = "InstallFailed";
const INTERFACE: &'static str = "com.subgraph.installer.Manager";
}

View File

@ -0,0 +1,11 @@
use std::result;
use dbus;
pub type Result<T> = result::Result<T, Error>;
#[derive(Debug)]
pub enum Error {
Dbus(dbus::Error),
Builder(String),
}

View File

@ -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::<&gtk::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::<Vec<_>>());
}

View File

@ -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<Option<String>>,
path: RefCell<Option<String>>,
size: RefCell<Option<String>>,
removable: RefCell<bool>,
}
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<Self>;
type Class = subclass::simple::ClassStruct<Self>;
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<glib::Value, ()> {
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::InstanceStruct<imp::RowData>, subclass::simple::ClassStruct<imp::RowData>, 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)
}
}
}

View File

@ -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<Msg>
}
impl Ui {
pub fn build(application: &gtk::Application) -> Result<Self> {
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::<RowData>().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::<gtk::Widget>()
});
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 <i>sudo journalctl -u citadel-installer-backend.service</i>\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<i>{}</i>\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<Vec<RowData>> {
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<String, Vec<String>>,) = 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: &gtk::Box, first_entry: &gtk::Entry, second_entry: &gtk::Entry, status_label: &gtk::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!("<i>{}</i>", 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::<RowData>().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(); }
});
}
}

View File

@ -3,6 +3,8 @@ name = "citadel-tool"
version = "0.1.0" version = "0.1.0"
authors = ["Bruce Leidl <bruce@subgraph.com>"] authors = ["Bruce Leidl <bruce@subgraph.com>"]
edition = "2018" edition = "2018"
description = "citadel-tool"
homepage = "https://subgraph.com"
[dependencies] [dependencies]
libcitadel = { path = "../libcitadel" } libcitadel = { path = "../libcitadel" }
@ -14,4 +16,5 @@ serde = "1.0"
toml = "0.5" toml = "0.5"
hex = "0.4" hex = "0.4"
byteorder = "1" byteorder = "1"
dbus = "0.8.4"
pwhash = "0.3.1"

View File

@ -5,6 +5,9 @@ use super::disk::Disk;
use rpassword; use rpassword;
use crate::install::installer::Installer; 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<bool> { pub fn run_cli_install() -> Result<bool> {
let disk = match choose_disk()? { let disk = match choose_disk()? {
Some(disk) => disk, Some(disk) => disk,
@ -13,7 +16,12 @@ pub fn run_cli_install() -> Result<bool> {
display_disk(&disk); 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, Some(passphrase) => passphrase,
None => return Ok(false), None => return Ok(false),
}; };
@ -21,7 +29,7 @@ pub fn run_cli_install() -> Result<bool> {
if !confirm_install(&disk)? { if !confirm_install(&disk)? {
return Ok(false); return Ok(false);
} }
run_install(disk, passphrase)?; run_install(disk, citadel_passphrase, passphrase)?;
Ok(true) Ok(true)
} }
@ -29,7 +37,12 @@ pub fn run_cli_install_with<P: AsRef<Path>>(target: P) -> Result<bool> {
let disk = find_disk_by_path(target.as_ref())?; let disk = find_disk_by_path(target.as_ref())?;
display_disk(&disk); 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, Some(passphrase) => passphrase,
None => return Ok(false), None => return Ok(false),
}; };
@ -38,12 +51,12 @@ pub fn run_cli_install_with<P: AsRef<Path>>(target: P) -> Result<bool> {
return Ok(false); return Ok(false);
} }
run_install(disk, passphrase)?; run_install(disk, citadel_passphrase, passphrase)?;
Ok(true) Ok(true)
} }
fn run_install(disk: Disk, passphrase: String) -> Result<()> { fn run_install(disk: Disk, citadel_passphrase: String, passphrase: String) -> Result<()> {
let mut install = Installer::new(disk.path(), &passphrase); let mut install = Installer::new(disk.path(), &citadel_passphrase, &passphrase);
install.set_install_syslinux(true); install.set_install_syslinux(true);
install.verify()?; install.verify()?;
install.run() install.run()
@ -108,9 +121,9 @@ fn read_line() -> Result<String> {
Ok(input) Ok(input)
} }
fn read_passphrase() -> io::Result<Option<String>> { fn read_passphrase(prompt: &str) -> io::Result<Option<String>> {
loop { loop {
println!("Enter a disk encryption passphrase (or 'q' to quit)"); println!("{}", prompt);
println!(); println!();
let passphrase = rpassword::read_password_from_tty(Some(" Passphrase : "))?; let passphrase = rpassword::read_password_from_tty(Some(" Passphrase : "))?;
if passphrase.is_empty() { if passphrase.is_empty() {

View File

@ -5,6 +5,7 @@ use std::os::unix::process::CommandExt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Command; use std::process::Command;
use std::time::Instant; use std::time::Instant;
use pwhash::sha512_crypt;
use libcitadel::util; use libcitadel::util;
use libcitadel::RealmFS; use libcitadel::RealmFS;
@ -121,20 +122,23 @@ pub struct Installer {
install_syslinux: bool, install_syslinux: bool,
storage_base: PathBuf, storage_base: PathBuf,
target_device: Option<PathBuf>, target_device: Option<PathBuf>,
citadel_passphrase: Option<String>,
passphrase: Option<String>, passphrase: Option<String>,
artifact_directory: String, artifact_directory: String,
logfile: Option<RefCell<File>>, logfile: Option<RefCell<File>>,
} }
impl Installer { impl Installer {
pub fn new<P: AsRef<Path>>(target_device: P, passphrase: &str) -> Installer { pub fn new<P: AsRef<Path>>(target_device: P, citadel_passphrase: &str, passphrase: &str) -> Installer {
let target_device = Some(target_device.as_ref().to_owned()); let target_device = Some(target_device.as_ref().to_owned());
let citadel_passphrase = Some(citadel_passphrase.to_owned());
let passphrase = Some(passphrase.to_owned()); let passphrase = Some(passphrase.to_owned());
Installer { Installer {
_type: InstallType::Install, _type: InstallType::Install,
install_syslinux: true, install_syslinux: true,
storage_base: PathBuf::from(INSTALL_MOUNT), storage_base: PathBuf::from(INSTALL_MOUNT),
target_device, target_device,
citadel_passphrase,
passphrase, passphrase,
artifact_directory: DEFAULT_ARTIFACT_DIRECTORY.to_string(), artifact_directory: DEFAULT_ARTIFACT_DIRECTORY.to_string(),
logfile: None, logfile: None,
@ -147,6 +151,7 @@ impl Installer {
install_syslinux: false, install_syslinux: false,
storage_base: PathBuf::from("/sysroot/storage"), storage_base: PathBuf::from("/sysroot/storage"),
target_device: None, target_device: None,
citadel_passphrase: None,
passphrase: None, passphrase: None,
artifact_directory: DEFAULT_ARTIFACT_DIRECTORY.to_string(), artifact_directory: DEFAULT_ARTIFACT_DIRECTORY.to_string(),
logfile: None, logfile: None,
@ -161,6 +166,10 @@ impl Installer {
self.target().to_str().unwrap() self.target().to_str().unwrap()
} }
fn citadel_passphrase(&self) -> &str {
self.citadel_passphrase.as_ref().expect("No citadel passphrase")
}
fn passphrase(&self) -> &str { fn passphrase(&self) -> &str {
self.passphrase.as_ref().expect("No passphrase") self.passphrase.as_ref().expect("No passphrase")
} }
@ -251,14 +260,14 @@ impl Installer {
Ok(()) Ok(())
} }
fn partition_disk(&self) -> Result<()> { pub fn partition_disk(&self) -> Result<()> {
self.header("Partitioning target disk")?; self.header("Partitioning target disk")?;
self.cmd_list(PARTITION_COMMANDS, &[ self.cmd_list(PARTITION_COMMANDS, &[
("$TARGET", self.target_str()) ("$TARGET", self.target_str())
]) ])
} }
fn setup_luks(&self) -> Result<()> { pub fn setup_luks(&self) -> Result<()> {
self.header("Setting up LUKS disk encryption")?; self.header("Setting up LUKS disk encryption")?;
util::create_dir(INSTALL_MOUNT)?; util::create_dir(INSTALL_MOUNT)?;
util::write_file(LUKS_PASSPHRASE_FILE, self.passphrase().as_bytes())?; util::write_file(LUKS_PASSPHRASE_FILE, self.passphrase().as_bytes())?;
@ -274,12 +283,12 @@ impl Installer {
util::remove_file(LUKS_PASSPHRASE_FILE) util::remove_file(LUKS_PASSPHRASE_FILE)
} }
fn setup_lvm(&self) -> Result<()> { pub fn setup_lvm(&self) -> Result<()> {
self.header("Setting up LVM volumes")?; self.header("Setting up LVM volumes")?;
self.cmd_list(LVM_COMMANDS, &[]) self.cmd_list(LVM_COMMANDS, &[])
} }
fn setup_boot(&self) -> Result<()> { pub fn setup_boot(&self) -> Result<()> {
self.header("Setting up /boot partition")?; self.header("Setting up /boot partition")?;
let boot_partition = self.target_partition(1); let boot_partition = self.target_partition(1);
self.cmd(format!("/sbin/mkfs.vfat -F 32 {}", boot_partition))?; 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())) util::copy_file(dent.path(), dst.join(dent.file_name()))
})?; })?;
let kernel_version = self.kernel_version();
self.info("Writing syslinux.cfg")?; self.info("Writing syslinux.cfg")?;
util::write_file(dst.join("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())) 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.header("Setting up /storage partition")?;
self.cmd_list(CREATE_STORAGE_COMMANDS, self.cmd_list(CREATE_STORAGE_COMMANDS,
@ -363,6 +374,7 @@ impl Installer {
self.setup_realm_skel()?; self.setup_realm_skel()?;
self.setup_main_realm()?; self.setup_main_realm()?;
self.setup_apt_cacher_realm()?; self.setup_apt_cacher_realm()?;
self.setup_citadel_passphrase()?;
self.info("Creating global realm config file")?; self.info("Creating global realm config file")?;
util::write_file(self.storage().join("realms/config"), self.global_realm_config())?; 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()) keyring.write(self.storage().join("keyring"), self.passphrase.as_ref().unwrap())
} }
fn setup_base_realmfs(&self) -> Result<()> { fn setup_base_realmfs(&self) -> Result<()> {
let realmfs_dir = self.storage().join("realms/realmfs-images"); let realmfs_dir = self.storage().join("realms/realmfs-images");
util::create_dir(&realmfs_dir)?; util::create_dir(&realmfs_dir)?;
@ -460,14 +473,37 @@ impl Installer {
self.sparse_copy_artifact(&kernel_img, &resources) 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")?; self.header("Installing rootfs partitions")?;
let rootfs = self.artifact_path("citadel-rootfs.img"); 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 {}", rootfs.display()))?;
self.cmd(format!("/usr/bin/citadel-image install-rootfs --skip-sha --no-prefer {}", 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, &[ self.cmd_list(FINISH_COMMANDS, &[
("$TARGET", self.target_str()) ("$TARGET", self.target_str())
]) ])

View File

@ -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>, 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<LocalConnection>,
//events: EventHandler,
}
impl DbusServer {
pub fn connect() -> Result<DbusServer> {
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<Msg>) -> Tree<MTFn<TData>, TData> {
let f = Factory::new_fn::<TData>();
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<Msg>) -> 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::<Msg>();
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<String, Vec<String>> {
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, "<TreeData>")
}
}
#[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 = ();
}

View File

@ -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<Vec<Disk>> {
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<Disk> {
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::<usize>()
.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
}
}

View File

@ -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(())
}

View File

@ -11,6 +11,7 @@ use libcitadel::RealmManager;
mod boot; mod boot;
mod image; mod image;
mod install; mod install;
mod install_backend;
mod mkimage; mod mkimage;
mod realmfs; mod realmfs;
mod sync; mod sync;
@ -30,6 +31,8 @@ fn main() {
boot::main(args); boot::main(args);
} else if exe == Path::new("/usr/libexec/citadel-install") { } else if exe == Path::new("/usr/libexec/citadel-install") {
install::main(args); 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") { } else if exe == Path::new("/usr/bin/citadel-image") {
image::main(args); image::main(args);
} else if exe == Path::new("/usr/bin/citadel-realmfs") { } else if exe == Path::new("/usr/bin/citadel-realmfs") {

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE busconfig PUBLIC
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
<policy user="root">
<allow own="com.subgraph.installer"/>
</policy>
<policy context="default">
<allow send_destination="com.subgraph.installer"/>
<allow send_destination="com.subgraph.installer"
send_interface="org.freedesktop.DBus.Properties"/>
<allow send_destination="com.subgraph.installer"
send_interface="org.freedesktop.DBus.Introspectable"/>
</policy>
</busconfig>