forked from brl/citadel-tools
Compare commits
2 Commits
updates
...
error_hand
| Author | SHA1 | Date | |
|---|---|---|---|
| 96f364cfe8 | |||
| 04457a47ef |
3372
Cargo.lock
generated
3372
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,5 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = ["citadel-realms", "citadel-tool", "realmsd", "launch-gnome-software", "update-realmfs"]
|
members = ["citadel-realms", "citadel-installer-ui", "citadel-tool", "realmsd", "realm-config-ui" ]
|
||||||
resolver = "2"
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|||||||
1
citadel-installer-ui/.gitignore
vendored
Normal file
1
citadel-installer-ui/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
750
citadel-installer-ui/Cargo.lock
generated
Normal file
750
citadel-installer-ui/Cargo.lock
generated
Normal 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"
|
||||||
19
citadel-installer-ui/Cargo.toml
Normal file
19
citadel-installer-ui/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "citadel-installer-ui"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["David McKinney <mckinney@subgraph.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
description = "Citadel Installer UI"
|
||||||
|
homepage = "https://subgraph.com"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
libcitadel = { path = "../libcitadel" }
|
||||||
|
failure = "0.1.8"
|
||||||
|
dbus = "0.8.4"
|
||||||
|
gtk = { version = "0.14.0", features = ["v3_24"] }
|
||||||
|
gio = "0.14.0"
|
||||||
|
glib = "0.14.0"
|
||||||
|
glib-macros = "0.14.0"
|
||||||
|
gdk = "0.14.0"
|
||||||
|
pango = "0.9.1"
|
||||||
|
once_cell = "1.0"
|
||||||
23
citadel-installer-ui/README.md
Normal file
23
citadel-installer-ui/README.md
Normal 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.
|
||||||
|
|
||||||
|
|
||||||
126
citadel-installer-ui/data/citadel_password_page.ui
Normal file
126
citadel-installer-ui/data/citadel_password_page.ui
Normal 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>
|
||||||
101
citadel-installer-ui/data/confirm_install_page.ui
Normal file
101
citadel-installer-ui/data/confirm_install_page.ui
Normal 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 <b>Apply</b> 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>
|
||||||
80
citadel-installer-ui/data/install_destination_page.ui
Normal file
80
citadel-installer-ui/data/install_destination_page.ui
Normal 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>
|
||||||
88
citadel-installer-ui/data/install_page.ui
Normal file
88
citadel-installer-ui/data/install_page.ui
Normal 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>
|
||||||
126
citadel-installer-ui/data/luks_password_page.ui
Normal file
126
citadel-installer-ui/data/luks_password_page.ui
Normal 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>
|
||||||
22
citadel-installer-ui/data/style.css
Normal file
22
citadel-installer-ui/data/style.css
Normal 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;
|
||||||
|
}
|
||||||
58
citadel-installer-ui/data/welcome_page.ui
Normal file
58
citadel-installer-ui/data/welcome_page.ui
Normal 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 <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.
|
||||||
|
</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>
|
||||||
48
citadel-installer-ui/src/builder.rs
Normal file
48
citadel-installer-ui/src/builder.rs
Normal 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.object(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_box(&self, name: &str) -> Result<gtk::Box> {
|
||||||
|
Self::ok_or_err("GtkBox", name, self.builder.object(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pub fn get_label(&self, name: &str) -> Result<gtk::Label> {
|
||||||
|
Self::ok_or_err("GtkLabel", name, self.builder.object(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_listbox(&self, name: &str) -> Result<gtk::ListBox> {
|
||||||
|
Self::ok_or_err("GtkListBox", name, self.builder.object(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_progress_bar(&self, name: &str) -> Result<gtk::ProgressBar> {
|
||||||
|
Self::ok_or_err("GtkProgressBar", name, self.builder.object(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_textview(&self, name: &str) -> Result<gtk::TextView> {
|
||||||
|
Self::ok_or_err("GtkTextView", name, self.builder.object(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_scrolled_window(&self, name: &str) -> Result<gtk::ScrolledWindow> {
|
||||||
|
Self::ok_or_err("GtkScrolledWindow", name, self.builder.object(name))
|
||||||
|
}
|
||||||
|
}
|
||||||
207
citadel-installer-ui/src/dbus_client.rs
Normal file
207
citadel-installer-ui/src/dbus_client.rs
Normal 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";
|
||||||
|
}
|
||||||
11
citadel-installer-ui/src/error.rs
Normal file
11
citadel-installer-ui/src/error.rs
Normal 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),
|
||||||
|
}
|
||||||
40
citadel-installer-ui/src/main.rs
Normal file
40
citadel-installer-ui/src/main.rs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#![allow(deprecated)]
|
||||||
|
use gtk::prelude::*;
|
||||||
|
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());
|
||||||
|
|
||||||
|
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 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();
|
||||||
|
}
|
||||||
130
citadel-installer-ui/src/rowdata.rs
Normal file
130
citadel-installer-ui/src/rowdata.rs
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
use gio::prelude::*;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use glib::subclass::prelude::*;
|
||||||
|
use glib::ParamSpec;
|
||||||
|
use gtk::glib;
|
||||||
|
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct RowDataImpl {
|
||||||
|
model: RefCell<Option<String>>,
|
||||||
|
path: RefCell<Option<String>>,
|
||||||
|
size: RefCell<Option<String>>,
|
||||||
|
removable: RefCell<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for RowDataImpl {
|
||||||
|
const NAME: &'static str = "RowData";
|
||||||
|
type Type = RowData;
|
||||||
|
type ParentType = glib::Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectImpl for RowDataImpl {
|
||||||
|
fn properties() -> &'static [ParamSpec] {
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||||
|
vec![
|
||||||
|
glib::ParamSpec::new_string(
|
||||||
|
"model",
|
||||||
|
"Model",
|
||||||
|
"Model",
|
||||||
|
None,
|
||||||
|
glib::ParamFlags::READWRITE,
|
||||||
|
),
|
||||||
|
glib::ParamSpec::new_string(
|
||||||
|
"path",
|
||||||
|
"Path",
|
||||||
|
"Path",
|
||||||
|
None,
|
||||||
|
glib::ParamFlags::READWRITE,
|
||||||
|
),
|
||||||
|
glib::ParamSpec::new_string(
|
||||||
|
"size",
|
||||||
|
"Size",
|
||||||
|
"Size",
|
||||||
|
None,
|
||||||
|
glib::ParamFlags::READWRITE,
|
||||||
|
),
|
||||||
|
glib::ParamSpec::new_boolean(
|
||||||
|
"removable",
|
||||||
|
"Removable",
|
||||||
|
"Removable",
|
||||||
|
false,
|
||||||
|
glib::ParamFlags::READWRITE,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
});
|
||||||
|
PROPERTIES.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_property(
|
||||||
|
&self,
|
||||||
|
_obj: &Self::Type,
|
||||||
|
_id: usize,
|
||||||
|
value: &glib::Value,
|
||||||
|
pspec: &glib::ParamSpec,
|
||||||
|
) {
|
||||||
|
match pspec.name() {
|
||||||
|
"model" => {
|
||||||
|
let model = value
|
||||||
|
.get()
|
||||||
|
.expect("type conformity checked by `Object::set_property`");
|
||||||
|
self.model.replace(model);
|
||||||
|
}
|
||||||
|
"path" => {
|
||||||
|
let path = value
|
||||||
|
.get()
|
||||||
|
.expect("type conformity checked by `Object::set_property`");
|
||||||
|
self.path.replace(path);
|
||||||
|
}
|
||||||
|
"size" => {
|
||||||
|
let size = value
|
||||||
|
.get()
|
||||||
|
.expect("type conformity checked by `Object::set_property`");
|
||||||
|
self.size.replace(size);
|
||||||
|
}
|
||||||
|
"removable" => {
|
||||||
|
let removable = value
|
||||||
|
.get()
|
||||||
|
.expect("type conformity checked by `Object::set_property`");
|
||||||
|
self.removable.replace(removable);
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||||
|
match pspec.name() {
|
||||||
|
"model" => self.model.borrow().to_value(),
|
||||||
|
"path" => self.path.borrow().to_value(),
|
||||||
|
"size" => self.size.borrow().to_value(),
|
||||||
|
"removable" => self.removable.borrow().to_value(),
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct RowData(ObjectSubclass<RowDataImpl>);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RowData {
|
||||||
|
pub fn new(model: &str, path: &str, size: &str, removable: bool) -> RowData {
|
||||||
|
glib::Object::new(&[
|
||||||
|
("model", &model),
|
||||||
|
("path", &path),
|
||||||
|
("size", &size),
|
||||||
|
("removable", &removable),
|
||||||
|
])
|
||||||
|
.expect("Failed to create row data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for RowData {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{:?}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
421
citadel-installer-ui/src/ui.rs
Normal file
421
citadel-installer-ui/src/ui.rs
Normal file
@@ -0,0 +1,421 @@
|
|||||||
|
use gtk::prelude::*;
|
||||||
|
use gtk::glib;
|
||||||
|
|
||||||
|
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::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 disk_rows: Vec<RowData>,
|
||||||
|
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: >k::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(glib::clone!(@strong application => move |_, _| {
|
||||||
|
application.quit();
|
||||||
|
gtk::Inhibit(false)
|
||||||
|
}));
|
||||||
|
assistant.connect_cancel(glib::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.property("removable").unwrap().get::<bool>().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(glib::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,
|
||||||
|
disk_rows: disks.clone(),
|
||||||
|
confirm_install_label,
|
||||||
|
install_page,
|
||||||
|
install_progress,
|
||||||
|
install_scrolled_window,
|
||||||
|
install_textview,
|
||||||
|
sender,
|
||||||
|
};
|
||||||
|
receiver.attach(None,glib::clone!(@strong ui, @strong application => move |msg| {
|
||||||
|
match msg {
|
||||||
|
Msg::InstallStarted => {
|
||||||
|
ui.install_progress.set_fraction(0.1428);
|
||||||
|
let buffer = ui.install_textview.buffer().unwrap();
|
||||||
|
let mut iter = buffer.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.buffer().unwrap();
|
||||||
|
let mut iter = buffer.end_iter();
|
||||||
|
buffer.insert(&mut iter, &text);
|
||||||
|
},
|
||||||
|
Msg::LvmSetup(text) => {
|
||||||
|
ui.install_progress.set_fraction(0.1428 * 3.0);
|
||||||
|
let buffer = ui.install_textview.buffer().unwrap();
|
||||||
|
let mut iter = buffer.end_iter();
|
||||||
|
buffer.insert(&mut iter, &text);
|
||||||
|
},
|
||||||
|
Msg::BootSetup(text) => {
|
||||||
|
ui.install_progress.set_fraction(0.1428 * 4.0);
|
||||||
|
let buffer = ui.install_textview.buffer().unwrap();
|
||||||
|
let mut iter = buffer.end_iter();
|
||||||
|
buffer.insert(&mut iter, &text);
|
||||||
|
},
|
||||||
|
Msg::StorageCreated(text) => {
|
||||||
|
ui.install_progress.set_fraction(0.1428 * 5.0);
|
||||||
|
let buffer = ui.install_textview.buffer().unwrap();
|
||||||
|
let mut iter = buffer.end_iter();
|
||||||
|
buffer.insert(&mut iter, &text);
|
||||||
|
},
|
||||||
|
Msg::RootfsInstalled(text) => {
|
||||||
|
ui.install_progress.set_fraction(0.1428 * 6.0);
|
||||||
|
let buffer = ui.install_textview.buffer().unwrap();
|
||||||
|
let mut iter = buffer.end_iter();
|
||||||
|
buffer.insert(&mut iter, &text);
|
||||||
|
},
|
||||||
|
Msg::InstallCompleted => {
|
||||||
|
ui.install_progress.set_fraction(1.0);
|
||||||
|
let buffer = ui.install_textview.buffer().unwrap();
|
||||||
|
let mut iter = buffer.end_iter();
|
||||||
|
buffer.insert(&mut iter, "+ Completed the installation successfully\n");
|
||||||
|
let quit_button = gtk::Button::with_label("Quit");
|
||||||
|
quit_button.connect_clicked(glib::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.buffer().unwrap();
|
||||||
|
let mut iter = buffer.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(glib::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: >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(glib::clone!(@weak assistant, @weak page, @weak second_entry, @weak status_label => move |entry| {
|
||||||
|
let password = entry.text();
|
||||||
|
let confirm = second_entry.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(glib::clone!(@weak second_entry => move |_| {
|
||||||
|
second_entry.grab_focus();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setup_prepare_signal(&self) {
|
||||||
|
let ui = self.clone();
|
||||||
|
ui.assistant.connect_prepare(glib::clone!(@strong ui => move |assistant, page| {
|
||||||
|
let page_type = assistant.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(glib::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(glib::clone!(@weak scrolled_window => move |_, _| {
|
||||||
|
let adjustment = scrolled_window.vadjustment();
|
||||||
|
adjustment.set_value(adjustment.upper() - adjustment.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::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.text();
|
||||||
|
password.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_luks_password(&self) -> String {
|
||||||
|
let ui = self.clone();
|
||||||
|
let password = ui.luks_password_entry.text();
|
||||||
|
password.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_install_destination(&self) -> String {
|
||||||
|
let ui = self.clone();
|
||||||
|
if let Some(row) = ui.disks_listbox.selected_row() {
|
||||||
|
let index = row.index() as usize;
|
||||||
|
if ui.disk_rows.len() > index {
|
||||||
|
let data = &ui.disk_rows[index];
|
||||||
|
let path: String = data.property("path").unwrap().get::<String>().unwrap();
|
||||||
|
return path.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"".to_string()
|
||||||
|
}
|
||||||
|
fn setup_signal_matchers(&self, proxy: Proxy<&Connection>) {
|
||||||
|
let sender = self.sender.clone();
|
||||||
|
let _ = proxy.match_signal(glib::clone!(@strong sender => move |_: ComSubgraphInstallerManagerInstallCompleted, _: &Connection, _: &Message| {
|
||||||
|
let _ = sender.send(Msg::InstallCompleted);
|
||||||
|
true
|
||||||
|
}));
|
||||||
|
let _ = proxy.match_signal(glib::clone!(@strong sender => move |h: ComSubgraphInstallerManagerLvmSetup, _: &Connection, _: &Message| {
|
||||||
|
let _ = sender.send(Msg::LvmSetup(h.text));
|
||||||
|
true
|
||||||
|
}));
|
||||||
|
let _ = proxy.match_signal(glib::clone!(@strong sender => move |h: ComSubgraphInstallerManagerLuksSetup, _: &Connection, _: &Message| {
|
||||||
|
let _ = sender.send(Msg::LuksSetup(h.text));
|
||||||
|
true
|
||||||
|
}));
|
||||||
|
let _ = proxy.match_signal(glib::clone!(@strong sender => move |h: ComSubgraphInstallerManagerBootSetup, _: &Connection, _: &Message| {
|
||||||
|
let _ = sender.send(Msg::BootSetup(h.text));
|
||||||
|
true
|
||||||
|
}));
|
||||||
|
let _ = proxy.match_signal(glib::clone!(@strong sender => move |h: ComSubgraphInstallerManagerStorageCreated, _: &Connection, _: &Message| {
|
||||||
|
let _ = sender.send(Msg::StorageCreated(h.text));
|
||||||
|
true
|
||||||
|
}));
|
||||||
|
let _ = proxy.match_signal(glib::clone!(@strong sender => move |h: ComSubgraphInstallerManagerRootfsInstalled, _: &Connection, _: &Message| {
|
||||||
|
let _ = sender.send(Msg::RootfsInstalled(h.text));
|
||||||
|
true
|
||||||
|
}));
|
||||||
|
let _ = proxy.match_signal(glib::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(); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,8 +21,6 @@ use cursive::vec::Vec2;
|
|||||||
use std::cell::{Cell, RefCell};
|
use std::cell::{Cell, RefCell};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self,BufWriter,Write};
|
use std::io::{self,BufWriter,Write};
|
||||||
use std::os::fd::AsFd;
|
|
||||||
use std::os::fd::OwnedFd;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
@@ -51,10 +49,19 @@ pub struct Backend {
|
|||||||
input_receiver: Receiver<TEvent>,
|
input_receiver: Receiver<TEvent>,
|
||||||
resize_receiver: Receiver<()>,
|
resize_receiver: Receiver<()>,
|
||||||
|
|
||||||
tty_fd: OwnedFd,
|
tty_fd: RawFd,
|
||||||
input_thread: JoinHandle<()>,
|
input_thread: JoinHandle<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn close_fd(fd: RawFd) -> io::Result<()> {
|
||||||
|
unsafe {
|
||||||
|
if libc::close(fd) != 0 {
|
||||||
|
return Err(io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn pthread_kill(tid: libc::pthread_t, sig: libc::c_int) -> io::Result<()> {
|
fn pthread_kill(tid: libc::pthread_t, sig: libc::c_int) -> io::Result<()> {
|
||||||
unsafe {
|
unsafe {
|
||||||
if libc::pthread_kill(tid, sig) != 0 {
|
if libc::pthread_kill(tid, sig) != 0 {
|
||||||
@@ -91,7 +98,7 @@ impl Backend {
|
|||||||
// Read input from a separate thread
|
// Read input from a separate thread
|
||||||
|
|
||||||
let input = std::fs::File::open("/dev/tty").unwrap();
|
let input = std::fs::File::open("/dev/tty").unwrap();
|
||||||
let tty_fd = input.as_fd().try_clone_to_owned().unwrap();
|
let tty_fd = input.as_raw_fd();
|
||||||
let input_thread = thread::spawn(move || {
|
let input_thread = thread::spawn(move || {
|
||||||
let mut events = input.events();
|
let mut events = input.events();
|
||||||
|
|
||||||
@@ -120,6 +127,12 @@ impl Backend {
|
|||||||
Ok(Box::new(c))
|
Ok(Box::new(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn close_tty(&self) {
|
||||||
|
if let Err(e) = close_fd(self.tty_fd) {
|
||||||
|
warn!("error closing tty fd: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn kill_thread(&self) {
|
fn kill_thread(&self) {
|
||||||
if let Err(e) = pthread_kill(self.input_thread.as_pthread_t(), libc::SIGWINCH) {
|
if let Err(e) = pthread_kill(self.input_thread.as_pthread_t(), libc::SIGWINCH) {
|
||||||
warn!("error sending signal to input thread: {}", e);
|
warn!("error sending signal to input thread: {}", e);
|
||||||
@@ -232,6 +245,7 @@ impl backend::Backend for Backend {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
self.close_tty();
|
||||||
self.kill_thread();
|
self.kill_thread();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,30 +8,14 @@ homepage = "https://subgraph.com"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libcitadel = { path = "../libcitadel" }
|
libcitadel = { path = "../libcitadel" }
|
||||||
rpassword = "7.3"
|
rpassword = "4.0"
|
||||||
clap = { version = "4.5", features = ["cargo", "derive"] }
|
clap = "2.33"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
toml = "0.9"
|
toml = "0.5"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
byteorder = "1"
|
byteorder = "1"
|
||||||
pwhash = "1.0"
|
dbus = "0.8.4"
|
||||||
|
pwhash = "0.3.1"
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
zbus = "5.9.0"
|
|
||||||
anyhow = "1.0"
|
|
||||||
log = "0.4"
|
|
||||||
zbus_macros = "5.9"
|
|
||||||
event-listener = "5.4"
|
|
||||||
futures-timer = "3.0"
|
|
||||||
tokio = { version = "1", features = ["full"] }
|
|
||||||
rs-release = "0.1"
|
|
||||||
glob = "0.3"
|
|
||||||
serde_cbor = "0.11"
|
|
||||||
ed25519-dalek = {version = "2.2", features = ["pem"]}
|
|
||||||
base64ct = "=1.7.3"
|
|
||||||
reqwest = { version = "0.12", features = ["blocking"] }
|
|
||||||
sha2 = "0.10"
|
|
||||||
nix = "0.30"
|
|
||||||
dialoguer = "0.12"
|
|
||||||
indicatif = "0.18"
|
|
||||||
@@ -4,7 +4,7 @@ use std::fs;
|
|||||||
use std::thread::{self,JoinHandle};
|
use std::thread::{self,JoinHandle};
|
||||||
use std::time::{self,Instant};
|
use std::time::{self,Instant};
|
||||||
|
|
||||||
use libcitadel::{Result, UtsName, util};
|
use libcitadel::{UtsName, util};
|
||||||
use libcitadel::ResourceImage;
|
use libcitadel::ResourceImage;
|
||||||
|
|
||||||
use crate::boot::disks;
|
use crate::boot::disks;
|
||||||
@@ -13,34 +13,33 @@ use crate::install::installer::Installer;
|
|||||||
|
|
||||||
const IMAGE_DIRECTORY: &str = "/run/citadel/images";
|
const IMAGE_DIRECTORY: &str = "/run/citadel/images";
|
||||||
|
|
||||||
pub fn live_rootfs() -> Result<()> {
|
pub fn live_rootfs() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
copy_artifacts()?;
|
copy_artifacts()?;
|
||||||
let rootfs = find_rootfs_image()?;
|
let rootfs = find_rootfs_image()?;
|
||||||
setup_rootfs_resource(&rootfs)
|
setup_rootfs_resource(&rootfs)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn live_setup() -> Result<()> {
|
pub fn live_setup() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
decompress_images(true)?;
|
decompress_images(true)?;
|
||||||
info!("Starting live setup");
|
info!("Starting live setup");
|
||||||
let live = Installer::new_livesetup();
|
let live = Installer::new_livesetup();
|
||||||
live.run()
|
live.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn copy_artifacts() -> Result<()> {
|
fn copy_artifacts() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
for _ in 0..3 {
|
for _ in 0..3 {
|
||||||
if try_copy_artifacts()? {
|
if try_copy_artifacts()? {
|
||||||
//decompress_images()?;
|
//decompress_images()?;
|
||||||
return Ok(())
|
return Ok(());
|
||||||
}
|
}
|
||||||
// Try again after waiting for more devices to be discovered
|
// Try again after waiting for more devices to be discovered
|
||||||
info!("Failed to find partition with images, trying again in 2 seconds");
|
info!("Failed to find partition with images, trying again in 2 seconds");
|
||||||
thread::sleep(time::Duration::from_secs(2));
|
thread::sleep(time::Duration::from_secs(2));
|
||||||
}
|
}
|
||||||
bail!("could not find partition containing resource images")
|
Result::Err("could not find partition containing resource images".into())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_copy_artifacts() -> Result<bool> {
|
fn try_copy_artifacts() -> Result<bool, Box<dyn std::error::Error>> {
|
||||||
let rootfs_image = Path::new("/boot/images/citadel-rootfs.img");
|
let rootfs_image = Path::new("/boot/images/citadel-rootfs.img");
|
||||||
// Already mounted?
|
// Already mounted?
|
||||||
if rootfs_image.exists() {
|
if rootfs_image.exists() {
|
||||||
@@ -60,13 +59,13 @@ fn try_copy_artifacts() -> Result<bool> {
|
|||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn kernel_version() -> String {
|
fn kernel_version() -> Result<String, Box<dyn std::error::Error>> {
|
||||||
let utsname = UtsName::uname();
|
let utsname = UtsName::uname();
|
||||||
let v = utsname.release().split('-').collect::<Vec<_>>();
|
let v = utsname.release().split('-').collect::<Vec<_>>();
|
||||||
v[0].to_string()
|
Ok(v[0].to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deploy_artifacts() -> Result<()> {
|
fn deploy_artifacts() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let run_images = Path::new(IMAGE_DIRECTORY);
|
let run_images = Path::new(IMAGE_DIRECTORY);
|
||||||
if !run_images.exists() {
|
if !run_images.exists() {
|
||||||
util::create_dir(run_images)?;
|
util::create_dir(run_images)?;
|
||||||
@@ -78,7 +77,7 @@ fn deploy_artifacts() -> Result<()> {
|
|||||||
util::copy_file(dent.path(), run_images.join(dent.file_name()))
|
util::copy_file(dent.path(), run_images.join(dent.file_name()))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let kv = kernel_version();
|
let kv = kernel_version()?;
|
||||||
println!("Copying bzImage-{} to /run/citadel/images", kv);
|
println!("Copying bzImage-{} to /run/citadel/images", kv);
|
||||||
let from = format!("/boot/bzImage-{}", kv);
|
let from = format!("/boot/bzImage-{}", kv);
|
||||||
let to = format!("/run/citadel/images/bzImage-{}", kv);
|
let to = format!("/run/citadel/images/bzImage-{}", kv);
|
||||||
@@ -92,7 +91,7 @@ fn deploy_artifacts() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deploy_syslinux_artifacts() -> Result<()> {
|
fn deploy_syslinux_artifacts() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let boot_syslinux = Path::new("/boot/syslinux");
|
let boot_syslinux = Path::new("/boot/syslinux");
|
||||||
|
|
||||||
if !boot_syslinux.exists() {
|
if !boot_syslinux.exists() {
|
||||||
@@ -112,10 +111,11 @@ fn deploy_syslinux_artifacts() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_rootfs_image() -> Result<ResourceImage> {
|
fn find_rootfs_image() -> Result<ResourceImage, Box<dyn std::error::Error>> {
|
||||||
let entries = fs::read_dir(IMAGE_DIRECTORY)
|
let entries = fs::read_dir(IMAGE_DIRECTORY)
|
||||||
.map_err(context!("error reading directory {}", IMAGE_DIRECTORY))?;
|
.map_err(context!("error reading directory {}", IMAGE_DIRECTORY))?;
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
@@ -123,15 +123,21 @@ fn find_rootfs_image() -> Result<ResourceImage> {
|
|||||||
if entry.path().extension() == Some(OsStr::new("img")) {
|
if entry.path().extension() == Some(OsStr::new("img")) {
|
||||||
if let Ok(image) = ResourceImage::from_path(&entry.path()) {
|
if let Ok(image) = ResourceImage::from_path(&entry.path()) {
|
||||||
if image.metainfo().image_type() == "rootfs" {
|
if image.metainfo().image_type() == "rootfs" {
|
||||||
return Ok(image)
|
return Ok(image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bail!("unable to find rootfs resource image in {}", IMAGE_DIRECTORY)
|
Result::Err(
|
||||||
|
format!(
|
||||||
|
"unable to find rootfs resource image in {}",
|
||||||
|
IMAGE_DIRECTORY
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decompress_images(sync: bool) -> Result<()> {
|
fn decompress_images(sync: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
info!("Decompressing images");
|
info!("Decompressing images");
|
||||||
let mut threads = Vec::new();
|
let mut threads = Vec::new();
|
||||||
util::read_directory("/run/citadel/images", |dent| {
|
util::read_directory("/run/citadel/images", |dent| {
|
||||||
@@ -140,9 +146,8 @@ fn decompress_images(sync: bool) -> Result<()> {
|
|||||||
if image.is_compressed() {
|
if image.is_compressed() {
|
||||||
if sync {
|
if sync {
|
||||||
if let Err(err) = decompress_one_image_sync(image) {
|
if let Err(err) = decompress_one_image_sync(image) {
|
||||||
warn!("Error: {}", err);
|
warn!("Error decompressing image: {}", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
threads.push(decompress_one_image(image));
|
threads.push(decompress_one_image(image));
|
||||||
}
|
}
|
||||||
@@ -161,7 +166,9 @@ fn decompress_images(sync: bool) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decompress_one_image_sync(image: ResourceImage) -> Result<()> {
|
fn decompress_one_image_sync(
|
||||||
|
image: ResourceImage,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
info!("Decompressing {}", image.path().display());
|
info!("Decompressing {}", image.path().display());
|
||||||
image.decompress(true)
|
image.decompress(true)
|
||||||
@@ -173,8 +180,7 @@ fn decompress_one_image_sync(image: ResourceImage) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decompress_one_image(image: ResourceImage) -> JoinHandle<Result<()>> {
|
fn decompress_one_image(image: ResourceImage,) ->
|
||||||
thread::spawn(move || {
|
JoinHandle<Result<(), Box<dyn std::error::Error + Send + Sync>>> {
|
||||||
decompress_one_image_sync(image)
|
thread::spawn(move || decompress_one_image_sync(image))
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::process::exit;
|
|
||||||
|
|
||||||
use libcitadel::{Result, ResourceImage, CommandLine, KeyRing, LogLevel, Logger, util};
|
use libcitadel::{ResourceImage, CommandLine, KeyRing, LogLevel, Logger, util};
|
||||||
use libcitadel::RealmManager;
|
use libcitadel::RealmManager;
|
||||||
use crate::boot::disks::DiskPartition;
|
use crate::boot::disks::DiskPartition;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -10,44 +9,40 @@ mod live;
|
|||||||
mod disks;
|
mod disks;
|
||||||
mod rootfs;
|
mod rootfs;
|
||||||
|
|
||||||
pub fn main(args: Vec<String>) {
|
pub fn main(args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
if CommandLine::debug() {
|
if CommandLine::debug() {
|
||||||
Logger::set_log_level(LogLevel::Debug);
|
Logger::set_log_level(LogLevel::Debug);
|
||||||
} else if CommandLine::verbose() {
|
} else if CommandLine::verbose() {
|
||||||
Logger::set_log_level(LogLevel::Info);
|
Logger::set_log_level(LogLevel::Info);
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = match args.get(1) {
|
match args.get(1) {
|
||||||
Some(s) if s == "rootfs" => do_rootfs(),
|
Some(s) if s == "rootfs" => do_rootfs(),
|
||||||
Some(s) if s == "setup" => do_setup(),
|
Some(s) if s == "setup" => do_setup(),
|
||||||
Some(s) if s == "boot-automount" => do_boot_automount(),
|
Some(s) if s == "boot-automount" => do_boot_automount(),
|
||||||
Some(s) if s == "start-realms" => do_start_realms(),
|
Some(s) if s == "start-realms" => do_start_realms(),
|
||||||
_ => Err(format_err!("Bad or missing argument").into()),
|
_ => Err(format_err!("Bad or missing argument").into()),
|
||||||
};
|
}?;
|
||||||
|
|
||||||
if let Err(ref e) = result {
|
Ok(())
|
||||||
warn!("Failed: {}", e);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn do_rootfs() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
fn do_rootfs() -> Result<()> {
|
|
||||||
if CommandLine::live_mode() || CommandLine::install_mode() {
|
if CommandLine::live_mode() || CommandLine::install_mode() {
|
||||||
live::live_rootfs()
|
live::live_rootfs()
|
||||||
} else {
|
} else {
|
||||||
rootfs::setup_rootfs()
|
Ok(rootfs::setup_rootfs()?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_keyring() -> Result<()> {
|
fn setup_keyring() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
ResourceImage::ensure_storage_mounted()?;
|
ResourceImage::ensure_storage_mounted()?;
|
||||||
let keyring = KeyRing::load_with_cryptsetup_passphrase("/sysroot/storage/keyring")?;
|
let keyring = KeyRing::load_with_cryptsetup_passphrase("/sysroot/storage/keyring")?;
|
||||||
keyring.add_keys_to_kernel()?;
|
keyring.add_keys_to_kernel()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_setup() -> Result<()> {
|
fn do_setup() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
if CommandLine::live_mode() || CommandLine::install_mode() {
|
if CommandLine::live_mode() || CommandLine::install_mode() {
|
||||||
live::live_setup()?;
|
live::live_setup()?;
|
||||||
} else if let Err(err) = setup_keyring() {
|
} else if let Err(err) = setup_keyring() {
|
||||||
@@ -64,8 +59,7 @@ fn do_setup() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn mount_overlay() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
fn mount_overlay() -> Result<()> {
|
|
||||||
info!("Creating rootfs overlay");
|
info!("Creating rootfs overlay");
|
||||||
|
|
||||||
info!("Moving /sysroot mount to /rootfs.ro");
|
info!("Moving /sysroot mount to /rootfs.ro");
|
||||||
@@ -89,13 +83,13 @@ fn mount_overlay() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_start_realms() -> Result<()> {
|
fn do_start_realms() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let manager = RealmManager::load()?;
|
let manager = RealmManager::load()?;
|
||||||
manager.start_boot_realms()
|
Ok(manager.start_boot_realms()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write automount unit for /boot partition
|
// Write automount unit for /boot partition
|
||||||
fn do_boot_automount() -> Result<()> {
|
fn do_boot_automount() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
Logger::set_log_level(LogLevel::Info);
|
Logger::set_log_level(LogLevel::Info);
|
||||||
|
|
||||||
if CommandLine::live_mode() || CommandLine::install_mode() {
|
if CommandLine::live_mode() || CommandLine::install_mode() {
|
||||||
@@ -105,10 +99,10 @@ fn do_boot_automount() -> Result<()> {
|
|||||||
|
|
||||||
let boot_partition = find_boot_partition()?;
|
let boot_partition = find_boot_partition()?;
|
||||||
info!("Creating /boot automount units for boot partition {}", boot_partition);
|
info!("Creating /boot automount units for boot partition {}", boot_partition);
|
||||||
cmd!("/usr/bin/systemd-mount", "-A --timeout-idle-sec=300 {} /boot", boot_partition)
|
Ok(cmd!("/usr/bin/systemd-mount", "-A --timeout-idle-sec=300 {} /boot", boot_partition)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_boot_partition() -> Result<String> {
|
fn find_boot_partition() -> Result<String, Box<dyn std::error::Error>> {
|
||||||
let loader_dev = read_loader_dev_efi_var()?;
|
let loader_dev = read_loader_dev_efi_var()?;
|
||||||
let boot_partitions = DiskPartition::boot_partitions(true)?
|
let boot_partitions = DiskPartition::boot_partitions(true)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -116,7 +110,7 @@ fn find_boot_partition() -> Result<String> {
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if boot_partitions.len() != 1 {
|
if boot_partitions.len() != 1 {
|
||||||
return Err(format_err!("Cannot uniquely determine boot partition"));
|
return Result::Err("Cannot uniquely determine boot partition".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(boot_partitions[0].path().display().to_string())
|
Ok(boot_partitions[0].path().display().to_string())
|
||||||
@@ -141,7 +135,7 @@ fn matches_loader_dev(partition: &DiskPartition, dev: &Option<String>) -> bool {
|
|||||||
const LOADER_EFI_VAR_PATH: &str =
|
const LOADER_EFI_VAR_PATH: &str =
|
||||||
"/sys/firmware/efi/efivars/LoaderDevicePartUUID-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f";
|
"/sys/firmware/efi/efivars/LoaderDevicePartUUID-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f";
|
||||||
|
|
||||||
fn read_loader_dev_efi_var() -> Result<Option<String>> {
|
fn read_loader_dev_efi_var() -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||||||
let efi_var = Path::new(LOADER_EFI_VAR_PATH);
|
let efi_var = Path::new(LOADER_EFI_VAR_PATH);
|
||||||
if efi_var.exists() {
|
if efi_var.exists() {
|
||||||
let s = fs::read(efi_var)
|
let s = fs::read(efi_var)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::{Command,Stdio};
|
use std::process::{Command,Stdio};
|
||||||
|
|
||||||
use libcitadel::{BlockDev, ResourceImage, CommandLine, ImageHeader, Partition, Result, LoopDevice};
|
use libcitadel::{BlockDev, ResourceImage, CommandLine, ImageHeader, Partition, LoopDevice};
|
||||||
use libcitadel::verity::Verity;
|
use libcitadel::verity::Verity;
|
||||||
|
|
||||||
pub fn setup_rootfs() -> Result<()> {
|
pub fn setup_rootfs() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut p = choose_boot_partiton(true, CommandLine::revert_rootfs())?;
|
let mut p = choose_boot_partiton(true, CommandLine::revert_rootfs())?;
|
||||||
if CommandLine::noverity() {
|
if CommandLine::noverity() {
|
||||||
setup_partition_unverified(&p)
|
setup_partition_unverified(&p)
|
||||||
@@ -13,7 +13,7 @@ pub fn setup_rootfs() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup_rootfs_resource(rootfs: &ResourceImage) -> Result<()> {
|
pub fn setup_rootfs_resource(rootfs: &ResourceImage) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
if CommandLine::noverity() {
|
if CommandLine::noverity() {
|
||||||
setup_resource_unverified(&rootfs)
|
setup_resource_unverified(&rootfs)
|
||||||
} else {
|
} else {
|
||||||
@@ -21,7 +21,7 @@ pub fn setup_rootfs_resource(rootfs: &ResourceImage) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_resource_unverified(img: &ResourceImage) -> Result<()> {
|
fn setup_resource_unverified(img: &ResourceImage) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
if img.is_compressed() {
|
if img.is_compressed() {
|
||||||
img.decompress(false)?;
|
img.decompress(false)?;
|
||||||
}
|
}
|
||||||
@@ -30,25 +30,31 @@ fn setup_resource_unverified(img: &ResourceImage) -> Result<()> {
|
|||||||
setup_linear_mapping(loopdev.device())
|
setup_linear_mapping(loopdev.device())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_resource_verified(img: &ResourceImage) -> Result<()> {
|
fn setup_resource_verified(img: &ResourceImage) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let _ = img.setup_verity_device()?;
|
let _ = img.setup_verity_device()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_partition_unverified(p: &Partition) -> Result<()> {
|
fn setup_partition_unverified(p: &Partition) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
info!("Creating /dev/mapper/rootfs device with linear device mapping of partition (no verity)");
|
info!("Creating /dev/mapper/rootfs device with linear device mapping of partition (no verity)");
|
||||||
setup_linear_mapping(p.path())
|
setup_linear_mapping(p.path())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_partition_verified(p: &mut Partition) -> Result<()> {
|
fn setup_partition_verified(p: &mut Partition) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
info!("Creating /dev/mapper/rootfs dm-verity device");
|
info!("Creating /dev/mapper/rootfs dm-verity device");
|
||||||
if !CommandLine::nosignatures() {
|
if !CommandLine::nosignatures() {
|
||||||
if !p.has_public_key() {
|
if !p.has_public_key() {
|
||||||
bail!("no public key available for channel {}", p.metainfo().channel())
|
return Result::Err(
|
||||||
|
format!(
|
||||||
|
"no public key available for channel {}",
|
||||||
|
p.metainfo().channel()
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if !p.is_signature_valid() {
|
if !p.is_signature_valid() {
|
||||||
p.write_status(ImageHeader::STATUS_BAD_SIG)?;
|
p.write_status(ImageHeader::STATUS_BAD_SIG)?;
|
||||||
bail!("signature verification failed on partition");
|
return Result::Err("signature verification failed on partition".into());
|
||||||
}
|
}
|
||||||
info!("Image signature is valid for channel {}", p.metainfo().channel());
|
info!("Image signature is valid for channel {}", p.metainfo().channel());
|
||||||
}
|
}
|
||||||
@@ -56,7 +62,7 @@ fn setup_partition_verified(p: &mut Partition) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_linear_mapping(blockdev: &Path) -> Result<()> {
|
fn setup_linear_mapping(blockdev: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let dev = BlockDev::open_ro(blockdev)?;
|
let dev = BlockDev::open_ro(blockdev)?;
|
||||||
let table = format!("0 {} linear {} 0", dev.nsectors()?, blockdev.display());
|
let table = format!("0 {} linear {} 0", dev.nsectors()?, blockdev.display());
|
||||||
|
|
||||||
@@ -70,7 +76,9 @@ fn setup_linear_mapping(blockdev: &Path) -> Result<()> {
|
|||||||
.success();
|
.success();
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
bail!("failed to set up linear identity mapping with /usr/sbin/dmsetup");
|
return Result::Err(
|
||||||
|
"failed to set up linear identity mapping with /usr/sbin/dmsetup".into(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -94,7 +102,8 @@ fn choose_revert_partition(best: Option<Partition>) -> Option<Partition> {
|
|||||||
best
|
best
|
||||||
}
|
}
|
||||||
|
|
||||||
fn choose_boot_partiton(scan: bool, revert_rootfs: bool) -> Result<Partition> {
|
fn choose_boot_partiton(scan: bool, revert_rootfs: bool,
|
||||||
|
) -> Result<Partition, Box<dyn std::error::Error>> {
|
||||||
let mut partitions = Partition::rootfs_partitions()?;
|
let mut partitions = Partition::rootfs_partitions()?;
|
||||||
|
|
||||||
if scan {
|
if scan {
|
||||||
@@ -136,16 +145,17 @@ fn compare_boot_partitions(a: Option<Partition>, b: Partition) -> Option<Partiti
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compare versions and channels
|
// Compare versions and channels
|
||||||
let bind_a = a.metainfo();
|
let meta_a = a.metainfo();
|
||||||
let bind_b = b.metainfo();
|
let meta_b = b.metainfo();
|
||||||
let a_v = bind_a.version();
|
|
||||||
let b_v = bind_b.version();
|
let ver_a = meta_a.version();
|
||||||
|
let ver_b = meta_b.version();
|
||||||
|
|
||||||
// Compare versions only if channels match
|
// Compare versions only if channels match
|
||||||
if a.metainfo().channel() == b.metainfo().channel() {
|
if a.metainfo().channel() == b.metainfo().channel() {
|
||||||
if a_v > b_v {
|
if ver_a > ver_b {
|
||||||
return Some(a);
|
return Some(a);
|
||||||
} else if b_v > a_v {
|
} else if ver_b > ver_a {
|
||||||
return Some(b);
|
return Some(b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,529 +0,0 @@
|
|||||||
use crate::{update, Path};
|
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use clap::ArgMatches;
|
|
||||||
use dialoguer::{theme::ColorfulTheme, Confirm, MultiSelect};
|
|
||||||
use ed25519_dalek::{pkcs8::DecodePublicKey, VerifyingKey};
|
|
||||||
use indicatif::{ProgressBar, ProgressStyle};
|
|
||||||
use libcitadel::ResourceImage;
|
|
||||||
use libcitadel::{updates, updates::CitadelVersionStruct};
|
|
||||||
use serde::Deserialize;
|
|
||||||
use sha2::{Digest, Sha256};
|
|
||||||
use std::env;
|
|
||||||
use std::fs;
|
|
||||||
use std::io::prelude::*;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use tempfile::Builder;
|
|
||||||
|
|
||||||
const IMAGE_DIRECTORY_PATH: &str = "/storage/resources";
|
|
||||||
const UPDATE_SERVER_KEY_PATH: &str = "/etc/citadel/update_server_key.pub";
|
|
||||||
const LAST_RESORT_CLIENT: &str = "public";
|
|
||||||
const LAST_RESORT_CHANNEL: &str = "stable";
|
|
||||||
const LAST_RESORT_CITADEL_PUBLISHER: &str = "Subgraph";
|
|
||||||
const DEFAULT_UPDATE_SERVER_HOSTNAME: &str = "update.subgraph.com";
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct Channels {
|
|
||||||
channels: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_update_server_hostname() -> String {
|
|
||||||
env::var("UPDATE_SERVER_HOSTNAME")
|
|
||||||
.unwrap_or_else(|_| DEFAULT_UPDATE_SERVER_HOSTNAME.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn verify_hash(path: &std::path::PathBuf, expected_hash: &str) -> Result<()> {
|
|
||||||
let mut file = fs::File::open(path)?;
|
|
||||||
let mut sha256 = Sha256::new();
|
|
||||||
std::io::copy(&mut file, &mut sha256)?;
|
|
||||||
let hash = format!("{:x}", sha256.finalize());
|
|
||||||
if hash != expected_hash {
|
|
||||||
fs::remove_file(path)?;
|
|
||||||
anyhow::bail!(
|
|
||||||
"Hash mismatch for file {}. Expected {}, got {}",
|
|
||||||
path.display(),
|
|
||||||
expected_hash,
|
|
||||||
hash
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check() -> Result<()> {
|
|
||||||
let current_version = get_current_os_config()?;
|
|
||||||
let server_citadel_version = fetch_and_verify_version_cbor(¤t_version)?;
|
|
||||||
let components_to_upgrade =
|
|
||||||
compare_citadel_versions(¤t_version, &server_citadel_version)?;
|
|
||||||
if components_to_upgrade.len() == 1 {
|
|
||||||
println!(
|
|
||||||
"We found the following component to upgrade: {}",
|
|
||||||
components_to_upgrade[0]
|
|
||||||
);
|
|
||||||
} else if components_to_upgrade.len() > 1 {
|
|
||||||
println!("We found the following components to upgrade: \n");
|
|
||||||
for component in components_to_upgrade {
|
|
||||||
println!("{}", component);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
println!("Your system is up to date!");
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_component_info_from_args<'a>(
|
|
||||||
sub_matches: &ArgMatches,
|
|
||||||
server_version: &'a CitadelVersionStruct,
|
|
||||||
) -> Result<(&'a str, &'a str)> {
|
|
||||||
let (path, hash) = if sub_matches.get_flag("rootfs") {
|
|
||||||
(
|
|
||||||
&server_version.component_version[0].file_path,
|
|
||||||
&server_version.component_version[0].sha256_hash,
|
|
||||||
)
|
|
||||||
} else if sub_matches.get_flag("kernel") {
|
|
||||||
(
|
|
||||||
&server_version.component_version[1].file_path,
|
|
||||||
&server_version.component_version[1].sha256_hash,
|
|
||||||
)
|
|
||||||
} else if sub_matches.get_flag("extra") {
|
|
||||||
(
|
|
||||||
&server_version.component_version[2].file_path,
|
|
||||||
&server_version.component_version[2].sha256_hash,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
anyhow::bail!("No component specified for download/reinstall.");
|
|
||||||
};
|
|
||||||
Ok((path, hash))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn download(sub_matches: &ArgMatches) -> Result<()> {
|
|
||||||
let current_version = &get_current_os_config()?;
|
|
||||||
let server_citadel_version = &fetch_and_verify_version_cbor(current_version)?;
|
|
||||||
|
|
||||||
let (path, hash) = get_component_info_from_args(sub_matches, server_citadel_version)?;
|
|
||||||
|
|
||||||
download_file(path, hash)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_remote() -> Result<()> {
|
|
||||||
let server_citadel_version = fetch_and_verify_version_cbor(&get_current_os_config()?)?;
|
|
||||||
println!("Server offers:\n{}", server_citadel_version);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn upgrade() -> Result<()> {
|
|
||||||
// First, get access to the current citadel's parameters
|
|
||||||
let current_version = &get_current_os_config()?;
|
|
||||||
|
|
||||||
// Check if we are missing local version info, which happens after a channel switch
|
|
||||||
let mut missing_components = Vec::new();
|
|
||||||
for component in ¤t_version.component_version {
|
|
||||||
if (component.component == updates::Component::Kernel
|
|
||||||
|| component.component == updates::Component::Extra)
|
|
||||||
&& component.version == "0.0.0"
|
|
||||||
{
|
|
||||||
missing_components.push(component.component.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !missing_components.is_empty() {
|
|
||||||
println!("WARNING: Your system is missing local version information for the following components:");
|
|
||||||
for comp in &missing_components {
|
|
||||||
println!("- {}", comp);
|
|
||||||
}
|
|
||||||
println!(
|
|
||||||
"\nThis is expected if you have recently switched to the '{}' update channel.",
|
|
||||||
current_version.channel
|
|
||||||
);
|
|
||||||
println!("However, it means we cannot verify if the server's version is newer than your installed version.");
|
|
||||||
println!(
|
|
||||||
"This could expose you to a downgrade attack if the remote channel is compromised."
|
|
||||||
);
|
|
||||||
|
|
||||||
let confirmed = Confirm::with_theme(&ColorfulTheme::default())
|
|
||||||
.with_prompt(
|
|
||||||
"Do you want to proceed with fetching and installing the versions from the server?",
|
|
||||||
)
|
|
||||||
.default(false)
|
|
||||||
.interact()?;
|
|
||||||
|
|
||||||
if !confirmed {
|
|
||||||
println!("Upgrade aborted by user.");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let server_citadel_version = &fetch_and_verify_version_cbor(current_version)?;
|
|
||||||
|
|
||||||
// Find which components have updates available
|
|
||||||
let components_to_upgrade = compare_citadel_versions(current_version, server_citadel_version)?;
|
|
||||||
|
|
||||||
if components_to_upgrade.is_empty() {
|
|
||||||
println!("Your system is up to date!");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a list of formatted strings for the prompt
|
|
||||||
let prompt_items: Vec<String> = components_to_upgrade
|
|
||||||
.iter()
|
|
||||||
.map(|comp| {
|
|
||||||
// Find the currently installed version for a nice display
|
|
||||||
let current_comp_version = current_version
|
|
||||||
.component_version
|
|
||||||
.iter()
|
|
||||||
.find(|c| c.component == comp.component)
|
|
||||||
.map(|c| c.version.clone())
|
|
||||||
.unwrap_or_else(|| "N/A".to_string());
|
|
||||||
|
|
||||||
// If the current version is 0.0.0, display it as "not installed"
|
|
||||||
let display_version = if current_comp_version == "0.0.0" {
|
|
||||||
"not installed".to_string()
|
|
||||||
} else {
|
|
||||||
current_comp_version
|
|
||||||
};
|
|
||||||
|
|
||||||
format!(
|
|
||||||
"{} ({} -> {})",
|
|
||||||
comp.component, display_version, comp.version
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// Build and display the interactive checklist to the user
|
|
||||||
println!("Found available updates. Please make your selection:");
|
|
||||||
let selections = MultiSelect::with_theme(&ColorfulTheme::default())
|
|
||||||
.with_prompt("Select components to upgrade (use spacebar to select, enter to confirm)")
|
|
||||||
.items(&prompt_items)
|
|
||||||
.defaults(&vec![true; prompt_items.len()])
|
|
||||||
.interact()?;
|
|
||||||
|
|
||||||
if selections.is_empty() {
|
|
||||||
println!("No components selected. Aborting upgrade.");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a final list of only the components the user selected
|
|
||||||
let mut final_components_to_install = Vec::new();
|
|
||||||
for index in selections {
|
|
||||||
final_components_to_install.push(components_to_upgrade[index].clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
println!(
|
|
||||||
"\nPreparing to install {} component(s)...",
|
|
||||||
final_components_to_install.len()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Loop through the selected components and install them
|
|
||||||
for component in final_components_to_install {
|
|
||||||
println!("---");
|
|
||||||
println!("Upgrading {}", component.component);
|
|
||||||
let (_tmp_dir, save_path) = download_file(&component.file_path, &component.sha256_hash)?;
|
|
||||||
println!("Installing image...");
|
|
||||||
update::install_image(&save_path, 0)?;
|
|
||||||
println!("{} installed successfully!", component.component);
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("---");
|
|
||||||
println!("Update process finished.");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reinstall(sub_matches: &ArgMatches) -> Result<()> {
|
|
||||||
let current_version = &get_current_os_config()?;
|
|
||||||
let server_citadel_version = &fetch_and_verify_version_cbor(current_version)?;
|
|
||||||
|
|
||||||
let (path, hash) = get_component_info_from_args(sub_matches, server_citadel_version)?;
|
|
||||||
|
|
||||||
let (_tmp_dir, save_path) = download_file(path, hash)?;
|
|
||||||
update::install_image(&save_path, 0)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn status() -> Result<()> {
|
|
||||||
println!("Gathering local system information...");
|
|
||||||
let current_config = get_current_os_config()?;
|
|
||||||
|
|
||||||
println!("\n--- Citadel Update Status ---");
|
|
||||||
println!("Client: {}", current_config.client);
|
|
||||||
println!("Channel: {}", current_config.channel);
|
|
||||||
println!("Publisher: {}", current_config.publisher);
|
|
||||||
println!("\n--- Installed Components ---");
|
|
||||||
|
|
||||||
for component in current_config.component_version {
|
|
||||||
// Handle the case where a component might not be installed for the current channel
|
|
||||||
let version_display = if component.version == "0.0.0" {
|
|
||||||
"Not installed for this channel".to_string()
|
|
||||||
} else {
|
|
||||||
component.version
|
|
||||||
};
|
|
||||||
println!(
|
|
||||||
"{:<12}{}",
|
|
||||||
format!("{}:", component.component),
|
|
||||||
version_display
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a vec of ComponentVersion structs of the components which can be upgraded
|
|
||||||
|
|
||||||
fn compare_citadel_versions(
|
|
||||||
current: &CitadelVersionStruct,
|
|
||||||
offered: &CitadelVersionStruct,
|
|
||||||
) -> Result<Vec<updates::AvailableComponentVersion>> {
|
|
||||||
let mut update_vec: Vec<updates::AvailableComponentVersion> = Vec::new();
|
|
||||||
|
|
||||||
if current.channel != offered.channel {
|
|
||||||
anyhow::bail!(
|
|
||||||
"Update channel mismatch. Your system is on '{}', but the server is configured for '{}'. Please check your configuration.",
|
|
||||||
current.channel,
|
|
||||||
offered.channel
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if current.client != offered.client {
|
|
||||||
anyhow::bail!(
|
|
||||||
"Update client mismatch. Your system is configured as '{}', but the server is configured for '{}'. Please check your configuration.",
|
|
||||||
current.client,
|
|
||||||
offered.client
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if current.publisher != offered.publisher {
|
|
||||||
anyhow::bail!(
|
|
||||||
"Update publisher mismatch. Your system is configured for '{}', but the server is configured for '{}'. Please check your configuration.",
|
|
||||||
current.publisher,
|
|
||||||
offered.publisher
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in 0..current.component_version.len() {
|
|
||||||
if current.component_version[i] < offered.component_version[i] {
|
|
||||||
update_vec.push(offered.component_version[i].clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(update_vec)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_image_version<P: AsRef<Path>>(path: &P) -> Result<String> {
|
|
||||||
let resource_image = ResourceImage::from_path(path)?;
|
|
||||||
Ok(resource_image.metainfo().version().to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads and displays the currently configured update channel.
|
|
||||||
pub fn show_channel() -> Result<()> {
|
|
||||||
let channel = updates::get_citadel_conf("CITADEL_CHANNEL")?
|
|
||||||
.unwrap_or_else(|| LAST_RESORT_CHANNEL.to_string());
|
|
||||||
|
|
||||||
println!("Current update channel: {}", channel);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the system's update channel by writing to the config file.
|
|
||||||
pub fn set_channel(channel_to_set: &str) -> Result<()> {
|
|
||||||
// Validate the channel name against the server's list.
|
|
||||||
let available_channels = fetch_available_channels()?;
|
|
||||||
if !available_channels.contains(&channel_to_set.to_string()) {
|
|
||||||
anyhow::bail!(
|
|
||||||
"Channel '{}' is not a valid remote channel. Use 'channel list' to see options.",
|
|
||||||
channel_to_set
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the directory for the new channel already exists.
|
|
||||||
let channel_dir_path = format!("{IMAGE_DIRECTORY_PATH}/{channel_to_set}");
|
|
||||||
let path = Path::new(&channel_dir_path);
|
|
||||||
|
|
||||||
if !path.exists() {
|
|
||||||
println!(
|
|
||||||
"You are switching to a new channel ('{}') for the first time.",
|
|
||||||
channel_to_set
|
|
||||||
);
|
|
||||||
|
|
||||||
let confirmed = Confirm::with_theme(&ColorfulTheme::default())
|
|
||||||
.with_prompt(format!(
|
|
||||||
"Are you sure you want to change channels to: '{}'?",
|
|
||||||
channel_dir_path
|
|
||||||
))
|
|
||||||
.default(true)
|
|
||||||
.interact()?;
|
|
||||||
|
|
||||||
if confirmed {
|
|
||||||
fs::create_dir_all(path).context(format!(
|
|
||||||
"Failed to create directory for channel '{}'",
|
|
||||||
channel_to_set
|
|
||||||
))?;
|
|
||||||
println!("Directory created.");
|
|
||||||
} else {
|
|
||||||
println!("Channel switch aborted by user.");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updates::set_citadel_conf("CITADEL_CHANNEL", channel_to_set)?;
|
|
||||||
|
|
||||||
println!("\nUpdate channel has been set to: {}", channel_to_set);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_current_os_config() -> Result<updates::CitadelVersionStruct> {
|
|
||||||
let client = updates::get_citadel_conf("CITADEL_CLIENT")?
|
|
||||||
.unwrap_or_else(|| LAST_RESORT_CLIENT.to_string());
|
|
||||||
|
|
||||||
let channel = updates::get_citadel_conf("CITADEL_CHANNEL")?
|
|
||||||
.unwrap_or_else(|| LAST_RESORT_CHANNEL.to_string());
|
|
||||||
|
|
||||||
let mut kernel_version = String::from("0.0.0");
|
|
||||||
let glob_pattern = format!("{IMAGE_DIRECTORY_PATH}/{channel}/citadel-kernel*.img");
|
|
||||||
if let Ok(glob_results) = glob::glob(&glob_pattern) {
|
|
||||||
for path_result in glob_results {
|
|
||||||
if let Ok(path) = path_result {
|
|
||||||
// If we find a kernel image, try to get its version.
|
|
||||||
// If we succeed, update the version and stop looking.
|
|
||||||
if let Ok(version) = get_image_version(&path) {
|
|
||||||
kernel_version = version;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RootFS version is always available from the running system
|
|
||||||
let rootfs_version = updates::get_os_release("CITADEL_ROOTFS_VERSION")?.unwrap();
|
|
||||||
|
|
||||||
// Extra version - gracefully handle if not found
|
|
||||||
let mut extra_version = String::from("0.0.0");
|
|
||||||
let glob_pattern_extra = format!("{IMAGE_DIRECTORY_PATH}/{channel}/citadel-extra*.img");
|
|
||||||
if let Ok(glob_results) = glob::glob(&glob_pattern_extra) {
|
|
||||||
for path_result in glob_results {
|
|
||||||
if let Ok(path) = path_result {
|
|
||||||
if let Ok(version) = get_image_version(&path) {
|
|
||||||
extra_version = version;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let publisher = updates::get_citadel_conf("CITADEL_PUBLISHER")?
|
|
||||||
.unwrap_or_else(|| LAST_RESORT_CITADEL_PUBLISHER.to_string());
|
|
||||||
|
|
||||||
let mut component_version = Vec::new();
|
|
||||||
component_version.push(updates::AvailableComponentVersion {
|
|
||||||
component: updates::Component::Rootfs,
|
|
||||||
version: rootfs_version.to_owned(),
|
|
||||||
file_path: "".to_owned(),
|
|
||||||
sha256_hash: "".to_owned(),
|
|
||||||
});
|
|
||||||
component_version.push(updates::AvailableComponentVersion {
|
|
||||||
component: updates::Component::Kernel,
|
|
||||||
version: kernel_version.to_owned(),
|
|
||||||
file_path: "".to_owned(),
|
|
||||||
sha256_hash: "".to_owned(),
|
|
||||||
});
|
|
||||||
component_version.push(updates::AvailableComponentVersion {
|
|
||||||
component: updates::Component::Extra,
|
|
||||||
version: extra_version.to_owned(),
|
|
||||||
file_path: "".to_owned(),
|
|
||||||
sha256_hash: "".to_owned(),
|
|
||||||
});
|
|
||||||
let current_version_struct = updates::CitadelVersionStruct {
|
|
||||||
client: client.to_owned(),
|
|
||||||
channel: channel.to_owned(),
|
|
||||||
component_version,
|
|
||||||
publisher: publisher.to_owned(),
|
|
||||||
};
|
|
||||||
Ok(current_version_struct)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fetch_and_verify_version_cbor(
|
|
||||||
current_citadel_version: &updates::CitadelVersionStruct,
|
|
||||||
) -> Result<updates::CitadelVersionStruct> {
|
|
||||||
let url = format!(
|
|
||||||
"https://{}/{}/{}/version.cbor",
|
|
||||||
get_update_server_hostname(),
|
|
||||||
current_citadel_version.client,
|
|
||||||
current_citadel_version.channel
|
|
||||||
);
|
|
||||||
let version_file_bytes = reqwest::blocking::get(&url)?
|
|
||||||
.bytes()
|
|
||||||
.context(format!("Failed to get version_file_bytes from {}", url))?;
|
|
||||||
let crypto_container: updates::CryptoContainerFile =
|
|
||||||
serde_cbor::from_slice(&version_file_bytes)
|
|
||||||
.context(format!("Failed to parse version.cbor from {}", url))?;
|
|
||||||
// find update server public key kept in the rootfs
|
|
||||||
let mut file = std::fs::File::open(UPDATE_SERVER_KEY_PATH).context(format!(
|
|
||||||
"Failed to open update_server_key file from {}",
|
|
||||||
UPDATE_SERVER_KEY_PATH
|
|
||||||
))?;
|
|
||||||
let mut contents = String::new();
|
|
||||||
file.read_to_string(&mut contents)?;
|
|
||||||
let public_key = VerifyingKey::from_public_key_pem(&contents)
|
|
||||||
.context("Failed to parse public key from file.")?;
|
|
||||||
let signature = ed25519_dalek::Signature::from_str(&crypto_container.signature)?;
|
|
||||||
// verify signature
|
|
||||||
public_key.verify_strict(&crypto_container.serialized_citadel_version, &signature)?;
|
|
||||||
// construct the struct
|
|
||||||
let citadel_version_struct: updates::CitadelVersionStruct =
|
|
||||||
serde_cbor::from_slice(&crypto_container.serialized_citadel_version)?;
|
|
||||||
Ok(citadel_version_struct)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn download_file(path: &str, hash: &str) -> Result<(tempfile::TempDir, std::path::PathBuf)> {
|
|
||||||
let client = reqwest::blocking::Client::new();
|
|
||||||
let url = format!("https://{}/{}", get_update_server_hostname(), path);
|
|
||||||
println!("Downloading from {}", url);
|
|
||||||
|
|
||||||
let component_download_response = client.get(&url).send()?;
|
|
||||||
|
|
||||||
// Get the total size of the file from the server's response headers.
|
|
||||||
let total_size = component_download_response
|
|
||||||
.content_length()
|
|
||||||
.context("Failed to get content length from server")?;
|
|
||||||
|
|
||||||
// Create a new progress bar and set its style.
|
|
||||||
let pb = ProgressBar::new(total_size);
|
|
||||||
pb.set_style(
|
|
||||||
ProgressStyle::default_bar()
|
|
||||||
.template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})")?
|
|
||||||
.tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏", " "])
|
|
||||||
.progress_chars("=>-"),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create the temporary directory and destination file.
|
|
||||||
let tmp_dir = Builder::new().prefix("citadel-fetch").tempdir()?;
|
|
||||||
let file_name = Path::new(path).file_name().unwrap();
|
|
||||||
let dest_path = tmp_dir.path().join(file_name);
|
|
||||||
let mut dest_file = fs::File::create(&dest_path)?;
|
|
||||||
|
|
||||||
// Wrap the download stream with the progress bar.
|
|
||||||
let mut source = pb.wrap_read(component_download_response);
|
|
||||||
|
|
||||||
// Copy the stream to the file, which automatically updates the progress bar.
|
|
||||||
std::io::copy(&mut source, &mut dest_file)?;
|
|
||||||
|
|
||||||
println!("\nSaved file to {}\n", dest_path.display());
|
|
||||||
verify_hash(&dest_path, hash)?;
|
|
||||||
println!("File hash verified\n");
|
|
||||||
Ok((tmp_dir, dest_path))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_channels() -> Result<()> {
|
|
||||||
println!("Fetching available channels...");
|
|
||||||
let channels = fetch_available_channels()?;
|
|
||||||
println!("Available channels:");
|
|
||||||
for channel in channels {
|
|
||||||
println!("- {}", channel);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fetches the list of available channels from the remote server.
|
|
||||||
fn fetch_available_channels() -> Result<Vec<String>> {
|
|
||||||
let url = format!(
|
|
||||||
"https://{}/{}/channels.cbor",
|
|
||||||
get_update_server_hostname(),
|
|
||||||
LAST_RESORT_CLIENT
|
|
||||||
);
|
|
||||||
let response_bytes = reqwest::blocking::get(&url)?.bytes()?;
|
|
||||||
let available_channels: Channels = serde_cbor::from_slice(&response_bytes)
|
|
||||||
.context("Failed to parse channel list CBOR from server")?;
|
|
||||||
Ok(available_channels.channels)
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
use clap::{arg, command, ArgAction, Command};
|
|
||||||
use std::process::exit;
|
|
||||||
mod fetch;
|
|
||||||
use clap::Arg;
|
|
||||||
|
|
||||||
pub fn main() {
|
|
||||||
let matches = command!() // requires `cargo` feature
|
|
||||||
.subcommand_required(true)
|
|
||||||
.subcommand(Command::new("check").about("Check for updates from remote server"))
|
|
||||||
.subcommand(Command::new("status").about("Show the currently installed versions and update channel"))
|
|
||||||
.subcommand(
|
|
||||||
Command::new("download")
|
|
||||||
.about("Download a specific component from the server")
|
|
||||||
.arg(arg!(-r --rootfs "rootfs component").action(ArgAction::SetTrue))
|
|
||||||
.arg(arg!(-k --kernel "kernel component").action(ArgAction::SetTrue))
|
|
||||||
.arg(arg!(-e --extra "extra component").action(ArgAction::SetTrue))
|
|
||||||
.arg_required_else_help(true),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
Command::new("read-remote")
|
|
||||||
.about("Read the remote server and print information on versions offered"),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
Command::new("upgrade")
|
|
||||||
.about("Download and install components if the server has a more recent version than currently installed on the system")
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
Command::new("reinstall")
|
|
||||||
.about("Download and install a specific component even if the server's component version is not greater than currently installed")
|
|
||||||
.arg(arg!(-r --rootfs "rootfs component").action(ArgAction::SetTrue))
|
|
||||||
.arg(arg!(-k --kernel "kernel component").action(ArgAction::SetTrue))
|
|
||||||
.arg(arg!(-e --extra "extra component").action(ArgAction::SetTrue))
|
|
||||||
.arg_required_else_help(true),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
Command::new("channel")
|
|
||||||
.about("Manage the update channel")
|
|
||||||
.subcommand_required(true)
|
|
||||||
.subcommand(Command::new("list").about("List available channels from the server"))
|
|
||||||
.subcommand(Command::new("show").about("Show the current update channel"))
|
|
||||||
.subcommand(
|
|
||||||
Command::new("set")
|
|
||||||
.about("Set a new update channel (requires root)")
|
|
||||||
.arg(Arg::new("CHANNEL").required(true)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.get_matches();
|
|
||||||
let result = match matches.subcommand() {
|
|
||||||
Some(("check", _sub_matches)) => fetch::check(),
|
|
||||||
Some(("status", _sub_matches)) => fetch::status(),
|
|
||||||
Some(("download", sub_matches)) => fetch::download(sub_matches),
|
|
||||||
Some(("read-remote", _sub_matches)) => fetch::read_remote(),
|
|
||||||
Some(("upgrade", _sub_matches)) => {
|
|
||||||
let _ = require_root(); // Check for root privileges
|
|
||||||
fetch::upgrade()
|
|
||||||
}
|
|
||||||
Some(("reinstall", sub_matches)) => {
|
|
||||||
let _ = require_root(); // Check for root privileges.
|
|
||||||
fetch::reinstall(sub_matches)
|
|
||||||
}
|
|
||||||
Some(("channel", sub_matches)) => match sub_matches.subcommand() {
|
|
||||||
Some(("list", _)) => fetch::list_channels(),
|
|
||||||
Some(("show", _)) => fetch::show_channel(),
|
|
||||||
Some(("set", set_matches)) => {
|
|
||||||
let _ = require_root();
|
|
||||||
let channel_name = set_matches.get_one::<String>("CHANNEL").unwrap();
|
|
||||||
fetch::set_channel(channel_name)
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
},
|
|
||||||
_ => unreachable!("Please pass a subcommand"),
|
|
||||||
};
|
|
||||||
if let Err(ref e) = result {
|
|
||||||
eprintln!("Error: {}", e);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn require_root() -> anyhow::Result<()> {
|
|
||||||
if !nix::unistd::geteuid().is_root() {
|
|
||||||
// Using anyhow::bail! is a concise way to return an error.
|
|
||||||
anyhow::bail!("This command requires root privileges. Please try again with sudo.");
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,97 +1,88 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use clap::{Arg,ArgMatches};
|
|
||||||
use clap::{command, ArgAction, Command};
|
use clap::{App,Arg,SubCommand,ArgMatches};
|
||||||
|
use clap::AppSettings::*;
|
||||||
|
use libcitadel::{ResourceImage, Logger, LogLevel, Partition, KeyPair, ImageHeader, util};
|
||||||
use hex;
|
use hex;
|
||||||
|
|
||||||
use libcitadel::{Result, ResourceImage, Logger, LogLevel, Partition, KeyPair, ImageHeader, util};
|
pub fn main(args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let app = App::new("citadel-image")
|
||||||
pub fn main() {
|
|
||||||
let matches = command!()
|
|
||||||
.about("Citadel update image builder")
|
.about("Citadel update image builder")
|
||||||
.arg_required_else_help(true)
|
.settings(&[ArgRequiredElseHelp,ColoredHelp, DisableHelpSubcommand, DisableVersion, DeriveDisplayOrder])
|
||||||
.disable_help_subcommand(true)
|
|
||||||
.subcommand(
|
|
||||||
Command::new("metainfo")
|
|
||||||
.about("Display metainfo variables for an image file")
|
|
||||||
.arg(Arg::new("path").required(true).help("Path to image file")),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
Command::new("info")
|
|
||||||
.about("Display metainfo variables for an image file")
|
|
||||||
.arg(Arg::new("path").required(true).help("Path to image file")),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
Command::new("generate-verity")
|
|
||||||
.about("Generate dm-verity hash tree for an image file")
|
|
||||||
.arg(Arg::new("path").required(true).help("Path to image file")),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
Command::new("verify")
|
|
||||||
.about("Verify dm-verity hash tree for an image file")
|
|
||||||
.arg(
|
|
||||||
Arg::new("option")
|
|
||||||
.long("option")
|
|
||||||
.required(true)
|
|
||||||
.help("Path to image file"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
Command::new("install-rootfs")
|
|
||||||
.about("Install rootfs image file to a partition")
|
|
||||||
.arg(
|
|
||||||
Arg::new("choose")
|
|
||||||
.long("just-choose")
|
|
||||||
.action(ArgAction::SetTrue)
|
|
||||||
.help("Don't install anything, just show which partition would be chosen"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("skip-sha")
|
|
||||||
.long("skip-sha")
|
|
||||||
.action(ArgAction::SetTrue)
|
|
||||||
.help("Skip verification of header sha256 value"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("no-prefer")
|
|
||||||
.long("no-prefer")
|
|
||||||
.action(ArgAction::SetTrue)
|
|
||||||
.help("Don't set PREFER_BOOT flag"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("path")
|
|
||||||
.required_unless_present("choose")
|
|
||||||
.help("Path to image file"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.subcommand(Command::new("genkeys").about("Generate a pair of keys"))
|
|
||||||
.subcommand(
|
|
||||||
Command::new("decompress")
|
|
||||||
.about("Decompress a compressed image file")
|
|
||||||
.arg(Arg::new("path").required(true).help("Path to image file")),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
Command::new("bless")
|
|
||||||
.about("Mark currently mounted rootfs partition as successfully booted"),
|
|
||||||
)
|
|
||||||
.subcommand(
|
|
||||||
Command::new("verify-shasum")
|
|
||||||
.about("Verify the sha256 sum of the image")
|
|
||||||
.arg(Arg::new("path").required(true).help("Path to image file")),
|
|
||||||
)
|
|
||||||
.get_matches();
|
|
||||||
|
|
||||||
|
.subcommand(SubCommand::with_name("metainfo")
|
||||||
|
.about("Display metainfo variables for an image file")
|
||||||
|
.arg(Arg::with_name("path")
|
||||||
|
.required(true)
|
||||||
|
.help("Path to image file")))
|
||||||
|
|
||||||
|
.subcommand(SubCommand::with_name("info")
|
||||||
|
.about("Display metainfo variables for an image file")
|
||||||
|
.arg(Arg::with_name("path")
|
||||||
|
.required(true)
|
||||||
|
.help("Path to image file")))
|
||||||
|
|
||||||
|
.subcommand(SubCommand::with_name("generate-verity")
|
||||||
|
.about("Generate dm-verity hash tree for an image file")
|
||||||
|
.arg(Arg::with_name("path")
|
||||||
|
.required(true)
|
||||||
|
.help("Path to image file")))
|
||||||
|
|
||||||
|
.subcommand(SubCommand::with_name("verify")
|
||||||
|
.about("Verify dm-verity hash tree for an image file")
|
||||||
|
.arg(Arg::with_name("path")
|
||||||
|
.required(true)
|
||||||
|
.help("Path to image file")))
|
||||||
|
|
||||||
|
.subcommand(SubCommand::with_name("install-rootfs")
|
||||||
|
.about("Install rootfs image file to a partition")
|
||||||
|
.arg(Arg::with_name("choose")
|
||||||
|
.long("just-choose")
|
||||||
|
.help("Don't install anything, just show which partition would be chosen"))
|
||||||
|
.arg(Arg::with_name("skip-sha")
|
||||||
|
.long("skip-sha")
|
||||||
|
.help("Skip verification of header sha256 value"))
|
||||||
|
.arg(Arg::with_name("no-prefer")
|
||||||
|
.long("no-prefer")
|
||||||
|
.help("Don't set PREFER_BOOT flag"))
|
||||||
|
.arg(Arg::with_name("path")
|
||||||
|
.required_unless("choose")
|
||||||
|
.help("Path to image file")))
|
||||||
|
|
||||||
|
.subcommand(SubCommand::with_name("genkeys")
|
||||||
|
.about("Generate a pair of keys"))
|
||||||
|
|
||||||
|
.subcommand(SubCommand::with_name("decompress")
|
||||||
|
.about("Decompress a compressed image file")
|
||||||
|
.arg(Arg::with_name("path")
|
||||||
|
.required(true)
|
||||||
|
.help("Path to image file")))
|
||||||
|
|
||||||
|
.subcommand(SubCommand::with_name("bless")
|
||||||
|
.about("Mark currently mounted rootfs partition as successfully booted"))
|
||||||
|
|
||||||
|
.subcommand(SubCommand::with_name("verify-shasum")
|
||||||
|
.about("Verify the sha256 sum of the image")
|
||||||
|
.arg(Arg::with_name("path")
|
||||||
|
.required(true)
|
||||||
|
.help("Path to image file")));
|
||||||
|
|
||||||
|
Logger::set_log_level(LogLevel::Debug);
|
||||||
|
|
||||||
|
let matches = app.get_matches_from(args);
|
||||||
let result = match matches.subcommand() {
|
let result = match matches.subcommand() {
|
||||||
Some(("metainfo", sub_m)) => metainfo(sub_m),
|
("metainfo", Some(m)) => metainfo(m),
|
||||||
Some(("info", sub_m)) => info(sub_m),
|
("info", Some(m)) => info(m),
|
||||||
Some(("generate-verity", sub_m)) => generate_verity(sub_m),
|
("generate-verity", Some(m)) => generate_verity(m),
|
||||||
Some(("verify", sub_m)) => verify(sub_m),
|
("verify", Some(m)) => verify(m),
|
||||||
Some(("sign-image", sub_m)) => sign_image(sub_m),
|
("sign-image", Some(m)) => sign_image(m),
|
||||||
Some(("genkeys", _)) => genkeys(),
|
("genkeys", Some(_)) => genkeys(),
|
||||||
Some(("decompress", sub_m)) => decompress(sub_m),
|
("decompress", Some(m)) => decompress(m),
|
||||||
Some(("verify-shasum", sub_m)) => verify_shasum(sub_m),
|
("verify-shasum", Some(m)) => verify_shasum(m),
|
||||||
Some(("install-rootfs", sub_m)) => install_rootfs(sub_m),
|
("install-rootfs", Some(m)) => install_rootfs(m),
|
||||||
Some(("install", sub_m)) => install_image(sub_m),
|
("install", Some(m)) => install_image(m),
|
||||||
Some(("bless", _)) => bless(),
|
("bless", Some(_)) => bless(),
|
||||||
_ => Ok(()),
|
_ => Ok(()),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -99,40 +90,42 @@ pub fn main() {
|
|||||||
println!("Error: {}", e);
|
println!("Error: {}", e);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn info(arg_matches: &ArgMatches) -> Result<()> {
|
fn info(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let img = load_image(arg_matches)?;
|
let img = load_image(arg_matches)?;
|
||||||
print!("{}",String::from_utf8(img.header().metainfo_bytes())?);
|
print!("{}", String::from_utf8(img.header().metainfo_bytes())?);
|
||||||
info_signature(&img)?;
|
info_signature(&img)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn info_signature(img: &ResourceImage) -> Result<()> {
|
fn info_signature(img: &ResourceImage) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
if img.header().has_signature() {
|
if img.header().has_signature() {
|
||||||
println!("Signature: {}", hex::encode(&img.header().signature()));
|
println!("Signature: {}", hex::encode(&img.header().signature()));
|
||||||
} else {
|
} else {
|
||||||
println!("Signature: No Signature");
|
println!("Signature: No Signature");
|
||||||
}
|
}
|
||||||
match img.header().public_key() {
|
match img.header().public_key()? {
|
||||||
Ok(pubkey) => {
|
Some(pubkey) => {
|
||||||
if img.header().verify_signature(pubkey) {
|
if img.header().verify_signature(pubkey) {
|
||||||
println!("Signature is valid");
|
println!("Signature is valid");
|
||||||
} else {
|
} else {
|
||||||
println!("Signature verify FAILED");
|
println!("Signature verify FAILED");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(_) => { println!("No public key found for channel '{}'", img.metainfo().channel()) },
|
None => { println!("No public key found for channel '{}'", img.metainfo().channel()) },
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn metainfo(arg_matches: &ArgMatches) -> Result<()> {
|
fn metainfo(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let img = load_image(arg_matches)?;
|
let img = load_image(arg_matches)?;
|
||||||
print!("{}",String::from_utf8(img.header().metainfo_bytes())?);
|
print!("{}", String::from_utf8(img.header().metainfo_bytes())?);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_verity(arg_matches: &ArgMatches) -> Result<()> {
|
fn generate_verity(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let img = load_image(arg_matches)?;
|
let img = load_image(arg_matches)?;
|
||||||
if img.has_verity_hashtree() {
|
if img.has_verity_hashtree() {
|
||||||
info!("Image already has dm-verity hashtree appended, doing nothing.");
|
info!("Image already has dm-verity hashtree appended, doing nothing.");
|
||||||
@@ -142,7 +135,7 @@ fn generate_verity(arg_matches: &ArgMatches) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify(arg_matches: &ArgMatches) -> Result<()> {
|
fn verify(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let img = load_image(arg_matches)?;
|
let img = load_image(arg_matches)?;
|
||||||
let ok = img.verify_verity()?;
|
let ok = img.verify_verity()?;
|
||||||
if ok {
|
if ok {
|
||||||
@@ -153,7 +146,7 @@ fn verify(arg_matches: &ArgMatches) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify_shasum(arg_matches: &ArgMatches) -> Result<()> {
|
fn verify_shasum(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let img = load_image(arg_matches)?;
|
let img = load_image(arg_matches)?;
|
||||||
let shasum = img.generate_shasum()?;
|
let shasum = img.generate_shasum()?;
|
||||||
if shasum == img.metainfo().shasum() {
|
if shasum == img.metainfo().shasum() {
|
||||||
@@ -166,39 +159,37 @@ fn verify_shasum(arg_matches: &ArgMatches) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_image(arg_matches: &ArgMatches) -> Result<ResourceImage> {
|
fn load_image(arg_matches: &ArgMatches) -> Result<ResourceImage, Box<dyn std::error::Error>> {
|
||||||
let path = arg_matches.get_one::<String>("path")
|
let path = arg_matches.value_of("path").expect("path argument missing");
|
||||||
.expect("path argument missing");
|
|
||||||
|
|
||||||
if !Path::new(path).exists() {
|
if !Path::new(path).exists() {
|
||||||
bail!("Cannot load image {}: File does not exist", path);
|
panic!("Cannot load image {}: File does not exist", path);
|
||||||
}
|
}
|
||||||
let img = ResourceImage::from_path(path)?;
|
let img = ResourceImage::from_path(path)?;
|
||||||
if !img.is_valid_image() {
|
if !img.is_valid_image() {
|
||||||
bail!("File {} is not a valid image file", path);
|
panic!("File {} is not a valid image file", path);
|
||||||
}
|
}
|
||||||
Ok(img)
|
Ok(img)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_rootfs(arg_matches: &ArgMatches) -> Result<()> {
|
fn install_rootfs(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
if arg_matches.get_flag("choose") {
|
if arg_matches.is_present("choose") {
|
||||||
let _ = choose_install_partition(true)?;
|
let _ = choose_install_partition(true)?;
|
||||||
return Ok(())
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let img = load_image(arg_matches)?;
|
let img = load_image(arg_matches)?;
|
||||||
|
|
||||||
if !arg_matches.get_flag("skip-sha") {
|
if !arg_matches.is_present("skip-sha") {
|
||||||
info!("Verifying sha256 hash of image");
|
info!("Verifying sha256 hash of image");
|
||||||
let shasum = img.generate_shasum()?;
|
let shasum = img.generate_shasum()?;
|
||||||
if shasum != img.metainfo().shasum() {
|
if shasum != img.metainfo().shasum() {
|
||||||
bail!("image file does not have expected sha256 value");
|
panic!("image file does not have expected sha256 value");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let partition = choose_install_partition(true)?;
|
let partition = choose_install_partition(true)?;
|
||||||
|
|
||||||
if !arg_matches.get_flag("no-prefer") {
|
if !arg_matches.is_present("no-prefer") {
|
||||||
clear_prefer_boot()?;
|
clear_prefer_boot()?;
|
||||||
img.header().set_flag(ImageHeader::FLAG_PREFER_BOOT);
|
img.header().set_flag(ImageHeader::FLAG_PREFER_BOOT);
|
||||||
}
|
}
|
||||||
@@ -206,7 +197,7 @@ fn install_rootfs(arg_matches: &ArgMatches) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_prefer_boot() -> Result<()> {
|
fn clear_prefer_boot() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
for mut p in Partition::rootfs_partitions()? {
|
for mut p in Partition::rootfs_partitions()? {
|
||||||
if p.is_initialized() && p.header().has_flag(ImageHeader::FLAG_PREFER_BOOT) {
|
if p.is_initialized() && p.header().has_flag(ImageHeader::FLAG_PREFER_BOOT) {
|
||||||
p.clear_flag_and_write(ImageHeader::FLAG_PREFER_BOOT)?;
|
p.clear_flag_and_write(ImageHeader::FLAG_PREFER_BOOT)?;
|
||||||
@@ -215,16 +206,14 @@ fn clear_prefer_boot() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign_image(arg_matches: &ArgMatches) -> Result<()> {
|
fn sign_image(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let _img = load_image(arg_matches)?;
|
let _img = load_image(arg_matches)?;
|
||||||
info!("Not implemented yet");
|
info!("Not implemented yet");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_image(arg_matches: &ArgMatches) -> Result<()> {
|
fn install_image(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let source = arg_matches.get_one::<String>("path")
|
let source = arg_matches.value_of("path").expect("path argument missing");
|
||||||
.expect("path argument missing");
|
|
||||||
|
|
||||||
let img = load_image(arg_matches)?;
|
let img = load_image(arg_matches)?;
|
||||||
let _hdr = img.header();
|
let _hdr = img.header();
|
||||||
let metainfo = img.metainfo();
|
let metainfo = img.metainfo();
|
||||||
@@ -232,12 +221,12 @@ fn install_image(arg_matches: &ArgMatches) -> Result<()> {
|
|||||||
// XXX verify signature?
|
// XXX verify signature?
|
||||||
|
|
||||||
if !(metainfo.image_type() == "kernel" || metainfo.image_type() == "extra") {
|
if !(metainfo.image_type() == "kernel" || metainfo.image_type() == "extra") {
|
||||||
bail!("Cannot install image type {}", metainfo.image_type());
|
panic!("Cannot install image type {}", metainfo.image_type());
|
||||||
}
|
}
|
||||||
|
|
||||||
let shasum = img.generate_shasum()?;
|
let shasum = img.generate_shasum()?;
|
||||||
if shasum != img.metainfo().shasum() {
|
if shasum != img.metainfo().shasum() {
|
||||||
bail!("Image shasum does not match metainfo");
|
panic!("Image shasum does not match metainfo");
|
||||||
}
|
}
|
||||||
|
|
||||||
img.generate_verity_hashtree()?;
|
img.generate_verity_hashtree()?;
|
||||||
@@ -245,44 +234,49 @@ fn install_image(arg_matches: &ArgMatches) -> Result<()> {
|
|||||||
let filename = if metainfo.image_type() == "kernel" {
|
let filename = if metainfo.image_type() == "kernel" {
|
||||||
let kernel_version = match metainfo.kernel_version() {
|
let kernel_version = match metainfo.kernel_version() {
|
||||||
Some(version) => version,
|
Some(version) => version,
|
||||||
None => bail!("Kernel image does not have a kernel version field in metainfo"),
|
None => panic!("Kernel image does not have a kernel version field in metainfo"),
|
||||||
};
|
};
|
||||||
if kernel_version.chars().any(|c| c == '/') {
|
if kernel_version.chars().any(|c| c == '/') {
|
||||||
bail!("Kernel version field has / char");
|
panic!("Kernel version field has / char");
|
||||||
}
|
}
|
||||||
format!("citadel-kernel-{}-{}.img", kernel_version, metainfo.version())
|
format!("citadel-kernel-{}-{:03}.img", kernel_version, metainfo.version())
|
||||||
} else {
|
} else {
|
||||||
format!("citadel-extra-{}.img", metainfo.version())
|
format!("citadel-extra-{:03}.img", metainfo.version())
|
||||||
};
|
};
|
||||||
|
|
||||||
if !metainfo.channel().chars().all(|c| c.is_ascii_lowercase()) {
|
if !metainfo.channel().chars().all(|c| c.is_ascii_lowercase()) {
|
||||||
bail!("Refusing to build path from strange channel name {}", metainfo.channel());
|
panic!(
|
||||||
|
"Refusing to build path from strange channel name {}",
|
||||||
|
metainfo.channel()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
let image_dir = Path::new("/storage/resources").join(metainfo.channel());
|
let image_dir = Path::new("/storage/resources").join(metainfo.channel());
|
||||||
let image_dest = image_dir.join(filename);
|
let image_dest = image_dir.join(filename);
|
||||||
if image_dest.exists() {
|
if image_dest.exists() {
|
||||||
rotate(&image_dest)?;
|
rotate(&image_dest)?;
|
||||||
}
|
}
|
||||||
util::rename(source, &image_dest)
|
util::rename(source, &image_dest)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rotate(path: &Path) -> Result<()> {
|
fn rotate(path: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
if !path.exists() || path.file_name().is_none() {
|
if !path.exists() || path.file_name().is_none() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let filename = path.file_name().unwrap();
|
let filename = path.file_name().unwrap();
|
||||||
let dot_zero = path.with_file_name(format!("{}.0", filename.to_string_lossy()));
|
let dot_zero = path.with_file_name(format!("{}.0", filename.to_string_lossy()));
|
||||||
util::remove_file(&dot_zero)?;
|
util::remove_file(&dot_zero)?;
|
||||||
util::rename(path, &dot_zero)
|
util::rename(path, &dot_zero).unwrap();
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn genkeys() -> Result<()> {
|
fn genkeys() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let keypair = KeyPair::generate();
|
let keypair = KeyPair::generate();
|
||||||
println!("keypair = \"{}\"", keypair.to_hex());
|
println!("keypair = \"{}\"", keypair.to_hex());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decompress(arg_matches: &ArgMatches) -> Result<()> {
|
fn decompress(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let img = load_image(arg_matches)?;
|
let img = load_image(arg_matches)?;
|
||||||
if !img.is_compressed() {
|
if !img.is_compressed() {
|
||||||
info!("Image is not compressed, not decompressing.");
|
info!("Image is not compressed, not decompressing.");
|
||||||
@@ -292,7 +286,7 @@ fn decompress(arg_matches: &ArgMatches) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bless() -> Result<()> {
|
fn bless() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
for mut p in Partition::rootfs_partitions()? {
|
for mut p in Partition::rootfs_partitions()? {
|
||||||
if p.is_initialized() && p.is_mounted() {
|
if p.is_initialized() && p.is_mounted() {
|
||||||
p.bless()?;
|
p.bless()?;
|
||||||
@@ -311,7 +305,7 @@ fn bool_to_yesno(val: bool) -> &'static str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn choose_install_partition(verbose: bool) -> Result<Partition> {
|
fn choose_install_partition(verbose: bool) -> Result<Partition, Box<dyn std::error::Error>> {
|
||||||
let partitions = Partition::rootfs_partitions()?;
|
let partitions = Partition::rootfs_partitions()?;
|
||||||
|
|
||||||
if verbose {
|
if verbose {
|
||||||
@@ -326,9 +320,12 @@ fn choose_install_partition(verbose: bool) -> Result<Partition> {
|
|||||||
for p in &partitions {
|
for p in &partitions {
|
||||||
if !p.is_mounted() && !p.is_initialized() {
|
if !p.is_mounted() && !p.is_initialized() {
|
||||||
if verbose {
|
if verbose {
|
||||||
info!("Choosing {} because it is empty and not mounted", p.path().display());
|
info!(
|
||||||
|
"Choosing {} because it is empty and not mounted",
|
||||||
|
p.path().display()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return Ok(p.clone())
|
return Ok(p.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for p in &partitions {
|
for p in &partitions {
|
||||||
@@ -336,10 +333,10 @@ fn choose_install_partition(verbose: bool) -> Result<Partition> {
|
|||||||
if verbose {
|
if verbose {
|
||||||
info!("Choosing {} because it is not mounted", p.path().display());
|
info!("Choosing {} because it is not mounted", p.path().display());
|
||||||
info!("Header metainfo:");
|
info!("Header metainfo:");
|
||||||
print!("{}",String::from_utf8(p.header().metainfo_bytes())?);
|
print!("{}", String::from_utf8(p.header().metainfo_bytes())?);
|
||||||
}
|
}
|
||||||
return Ok(p.clone())
|
return Ok(p.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bail!("No suitable install partition found")
|
panic!("No suitable install partition found")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use std::io::{self,Write};
|
use std::io::{self,Write};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use libcitadel::Result;
|
|
||||||
use super::disk::Disk;
|
use super::disk::Disk;
|
||||||
use rpassword;
|
use rpassword;
|
||||||
use crate::install::installer::Installer;
|
use crate::install::installer::Installer;
|
||||||
@@ -8,7 +7,7 @@ use crate::install::installer::Installer;
|
|||||||
const CITADEL_PASSPHRASE_PROMPT: &str = "Enter a password for the Citadel user (or 'q' to quit)";
|
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";
|
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, Box<dyn std::error::Error>> {
|
||||||
let disk = match choose_disk()? {
|
let disk = match choose_disk()? {
|
||||||
Some(disk) => disk,
|
Some(disk) => disk,
|
||||||
None => return Ok(false),
|
None => return Ok(false),
|
||||||
@@ -33,7 +32,7 @@ pub fn run_cli_install() -> Result<bool> {
|
|||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_cli_install_with<P: AsRef<Path>>(target: P) -> Result<bool> {
|
pub fn run_cli_install_with<P: AsRef<Path>>(target: P) -> Result<bool, Box<dyn std::error::Error>> {
|
||||||
let disk = find_disk_by_path(target.as_ref())?;
|
let disk = find_disk_by_path(target.as_ref())?;
|
||||||
display_disk(&disk);
|
display_disk(&disk);
|
||||||
|
|
||||||
@@ -55,11 +54,15 @@ pub fn run_cli_install_with<P: AsRef<Path>>(target: P) -> Result<bool> {
|
|||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_install(disk: Disk, citadel_passphrase: String, passphrase: String) -> Result<()> {
|
fn run_install(
|
||||||
let mut install = Installer::new(disk.path(), &citadel_passphrase, &passphrase, None);
|
disk: Disk,
|
||||||
|
citadel_passphrase: String,
|
||||||
|
passphrase: String,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
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()
|
Ok(install.run()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_disk(disk: &Disk) {
|
fn display_disk(disk: &Disk) {
|
||||||
@@ -70,22 +73,22 @@ fn display_disk(disk: &Disk) {
|
|||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_disk_by_path(path: &Path) -> Result<Disk> {
|
fn find_disk_by_path(path: &Path) -> Result<Disk, Box<dyn std::error::Error>> {
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
bail!("Target disk path {} does not exist", path.display());
|
panic!("Target disk path {} does not exist", path.display());
|
||||||
}
|
}
|
||||||
for disk in Disk::probe_all()? {
|
for disk in Disk::probe_all()? {
|
||||||
if disk.path() == path {
|
if disk.path() == path {
|
||||||
return Ok(disk.clone());
|
return Ok(disk.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bail!("installation target {} is not a valid disk", path.display())
|
panic!("installation target {} is not a valid disk", path.display())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn choose_disk() -> Result<Option<Disk>> {
|
fn choose_disk() -> Result<Option<Disk>, Box<dyn std::error::Error>> {
|
||||||
let disks = Disk::probe_all()?;
|
let disks = Disk::probe_all()?;
|
||||||
if disks.is_empty() {
|
if disks.is_empty() {
|
||||||
bail!("no disks found.");
|
panic!("no disks found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@@ -96,7 +99,7 @@ fn choose_disk() -> Result<Option<Disk>> {
|
|||||||
}
|
}
|
||||||
if let Ok(n) = line.parse::<usize>() {
|
if let Ok(n) = line.parse::<usize>() {
|
||||||
if n > 0 && n <= disks.len() {
|
if n > 0 && n <= disks.len() {
|
||||||
return Ok(Some(disks[n-1].clone()));
|
return Ok(Some(disks[n - 1].clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,7 +114,7 @@ fn prompt_choose_disk(disks: &[Disk]) {
|
|||||||
let _ = io::stdout().flush();
|
let _ = io::stdout().flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_line() -> Result<String> {
|
fn read_line() -> Result<String, Box<dyn std::error::Error>> {
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
io::stdin().read_line(&mut input)
|
io::stdin().read_line(&mut input)
|
||||||
.map_err(context!("error reading line from stdin"))?;
|
.map_err(context!("error reading line from stdin"))?;
|
||||||
@@ -125,7 +128,7 @@ fn read_passphrase(prompt: &str) -> io::Result<Option<String>> {
|
|||||||
loop {
|
loop {
|
||||||
println!("{}", prompt);
|
println!("{}", prompt);
|
||||||
println!();
|
println!();
|
||||||
let passphrase = rpassword::prompt_password(" Passphrase : ")?;
|
let passphrase = rpassword::read_password_from_tty(Some(" Passphrase : "))?;
|
||||||
if passphrase.is_empty() {
|
if passphrase.is_empty() {
|
||||||
println!("Passphrase cannot be empty");
|
println!("Passphrase cannot be empty");
|
||||||
continue;
|
continue;
|
||||||
@@ -133,7 +136,7 @@ fn read_passphrase(prompt: &str) -> io::Result<Option<String>> {
|
|||||||
if passphrase == "q" || passphrase == "Q" {
|
if passphrase == "q" || passphrase == "Q" {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
let confirm = rpassword::prompt_password(" Confirm : ")?;
|
let confirm = rpassword::read_password_from_tty(Some(" Confirm : "))?;
|
||||||
if confirm == "q" || confirm == "Q" {
|
if confirm == "q" || confirm == "Q" {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
@@ -146,7 +149,7 @@ fn read_passphrase(prompt: &str) -> io::Result<Option<String>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm_install(disk: &Disk) -> Result<bool> {
|
fn confirm_install(disk: &Disk) -> Result<bool, Box<dyn std::error::Error>> {
|
||||||
println!("Are you sure you want to completely erase this this device?");
|
println!("Are you sure you want to completely erase this this device?");
|
||||||
println!();
|
println!();
|
||||||
println!(" Device: {}", disk.path().display());
|
println!(" Device: {}", disk.path().display());
|
||||||
@@ -158,4 +161,3 @@ fn confirm_install(disk: &Disk) -> Result<bool> {
|
|||||||
let answer = read_line()?;
|
let answer = read_line()?;
|
||||||
Ok(answer == "YES")
|
Ok(answer == "YES")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ use pwhash::sha512_crypt;
|
|||||||
|
|
||||||
use libcitadel::util;
|
use libcitadel::util;
|
||||||
use libcitadel::RealmFS;
|
use libcitadel::RealmFS;
|
||||||
use libcitadel::Result;
|
|
||||||
use libcitadel::OsRelease;
|
use libcitadel::OsRelease;
|
||||||
use libcitadel::KeyRing;
|
use libcitadel::KeyRing;
|
||||||
use libcitadel::terminal::Base16Scheme;
|
use libcitadel::terminal::Base16Scheme;
|
||||||
@@ -20,8 +19,6 @@ const LUKS_UUID: &str = "683a17fc-4457-42cc-a946-cde67195a101";
|
|||||||
const EXTRA_IMAGE_NAME: &str = "citadel-extra.img";
|
const EXTRA_IMAGE_NAME: &str = "citadel-extra.img";
|
||||||
|
|
||||||
const INSTALL_MOUNT: &str = "/run/installer/mnt";
|
const INSTALL_MOUNT: &str = "/run/installer/mnt";
|
||||||
const STORAGE_MOUNT: &str = "/run/installer/storage";
|
|
||||||
|
|
||||||
const LUKS_PASSPHRASE_FILE: &str = "/run/installer/luks-passphrase";
|
const LUKS_PASSPHRASE_FILE: &str = "/run/installer/luks-passphrase";
|
||||||
|
|
||||||
const DEFAULT_ARTIFACT_DIRECTORY: &str = "/run/citadel/images";
|
const DEFAULT_ARTIFACT_DIRECTORY: &str = "/run/citadel/images";
|
||||||
@@ -127,13 +124,12 @@ pub struct Installer {
|
|||||||
target_device: Option<PathBuf>,
|
target_device: Option<PathBuf>,
|
||||||
citadel_passphrase: Option<String>,
|
citadel_passphrase: Option<String>,
|
||||||
passphrase: Option<String>,
|
passphrase: Option<String>,
|
||||||
timezone: 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, citadel_passphrase: &str, passphrase: &str, timezone: Option<String>) -> 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 citadel_passphrase = Some(citadel_passphrase.to_owned());
|
||||||
let passphrase = Some(passphrase.to_owned());
|
let passphrase = Some(passphrase.to_owned());
|
||||||
@@ -144,7 +140,6 @@ impl Installer {
|
|||||||
target_device,
|
target_device,
|
||||||
citadel_passphrase,
|
citadel_passphrase,
|
||||||
passphrase,
|
passphrase,
|
||||||
timezone,
|
|
||||||
artifact_directory: DEFAULT_ARTIFACT_DIRECTORY.to_string(),
|
artifact_directory: DEFAULT_ARTIFACT_DIRECTORY.to_string(),
|
||||||
logfile: None,
|
logfile: None,
|
||||||
}
|
}
|
||||||
@@ -158,7 +153,6 @@ impl Installer {
|
|||||||
target_device: None,
|
target_device: None,
|
||||||
citadel_passphrase: None,
|
citadel_passphrase: None,
|
||||||
passphrase: None,
|
passphrase: None,
|
||||||
timezone: None,
|
|
||||||
artifact_directory: DEFAULT_ARTIFACT_DIRECTORY.to_string(),
|
artifact_directory: DEFAULT_ARTIFACT_DIRECTORY.to_string(),
|
||||||
logfile: None,
|
logfile: None,
|
||||||
}
|
}
|
||||||
@@ -188,7 +182,7 @@ impl Installer {
|
|||||||
self.install_syslinux = val;
|
self.install_syslinux = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify(&self) -> Result<()> {
|
pub fn verify(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let kernel_img = self.kernel_imagename();
|
let kernel_img = self.kernel_imagename();
|
||||||
let bzimage = format!("bzImage-{}", self.kernel_version());
|
let bzimage = format!("bzImage-{}", self.kernel_version());
|
||||||
let artifacts = vec![
|
let artifacts = vec![
|
||||||
@@ -197,26 +191,29 @@ impl Installer {
|
|||||||
];
|
];
|
||||||
|
|
||||||
if !self.target().exists() {
|
if !self.target().exists() {
|
||||||
bail!("target device {:?} does not exist", self.target());
|
panic!("target device {:?} does not exist", self.target());
|
||||||
}
|
}
|
||||||
|
|
||||||
for a in artifacts {
|
for a in artifacts {
|
||||||
if !self.artifact_path(a).exists() {
|
if !self.artifact_path(a).exists() {
|
||||||
bail!("required install artifact {} does not exist in {}", a, self.artifact_directory);
|
panic!(
|
||||||
|
"required install artifact {} does not exist in {}",
|
||||||
|
a, self.artifact_directory
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&self) -> Result<()> {
|
pub fn run(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
match self._type {
|
match self._type {
|
||||||
InstallType::Install => self.run_install(),
|
InstallType::Install => self.run_install(),
|
||||||
InstallType::LiveSetup => self.run_live_setup(),
|
InstallType::LiveSetup => self.run_live_setup(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_install(&self) -> Result<()> {
|
pub fn run_install(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
self.partition_disk()?;
|
self.partition_disk()?;
|
||||||
self.setup_luks()?;
|
self.setup_luks()?;
|
||||||
@@ -229,20 +226,7 @@ impl Installer {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup_localtime_symlink(&self, timezone: Option<String>) -> Result<()> {
|
pub fn run_live_setup(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
self.header("Setting up localtime symlink")?;
|
|
||||||
util::create_dir(STORAGE_MOUNT)?;
|
|
||||||
let storage_path = "/dev/mapper/citadel-storage";
|
|
||||||
self.cmd(format!("/bin/mount {} {}", storage_path, STORAGE_MOUNT))?;
|
|
||||||
let rootfs_localtime_path = Path::new("/usr/share/zoneinfo/").join(timezone.unwrap_or("Canada/Eastern".to_string()));
|
|
||||||
let storage_localtime_link = Path::new(STORAGE_MOUNT).join("citadel-state/localtime");
|
|
||||||
self.info(format!("Creating symlink from {} to {}", storage_localtime_link.display(), rootfs_localtime_path.display()))?;
|
|
||||||
util::symlink(&rootfs_localtime_path, storage_localtime_link)?;
|
|
||||||
self.cmd(format!("/bin/umount {}", STORAGE_MOUNT))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_live_setup(&self) -> Result<()> {
|
|
||||||
self.cmd_list(&[
|
self.cmd_list(&[
|
||||||
"/bin/mount -t tmpfs var-tmpfs /sysroot/var",
|
"/bin/mount -t tmpfs var-tmpfs /sysroot/var",
|
||||||
"/bin/mount -t tmpfs home-tmpfs /sysroot/home",
|
"/bin/mount -t tmpfs home-tmpfs /sysroot/home",
|
||||||
@@ -260,8 +244,7 @@ impl Installer {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_live_realm(&self) -> Result<()> {
|
fn setup_live_realm(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
||||||
let realmfs_dir = self.storage().join("realms/realmfs-images");
|
let realmfs_dir = self.storage().join("realms/realmfs-images");
|
||||||
let base_realmfs = realmfs_dir.join("base-realmfs.img");
|
let base_realmfs = realmfs_dir.join("base-realmfs.img");
|
||||||
|
|
||||||
@@ -279,14 +262,14 @@ impl Installer {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn partition_disk(&self) -> Result<()> {
|
pub fn partition_disk(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
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())
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup_luks(&self) -> Result<()> {
|
pub fn setup_luks(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
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())?;
|
||||||
@@ -299,15 +282,16 @@ impl Installer {
|
|||||||
("$LUKS_PASSFILE", LUKS_PASSPHRASE_FILE),
|
("$LUKS_PASSFILE", LUKS_PASSPHRASE_FILE),
|
||||||
])?;
|
])?;
|
||||||
|
|
||||||
util::remove_file(LUKS_PASSPHRASE_FILE)
|
util::remove_file(LUKS_PASSPHRASE_FILE)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup_lvm(&self) -> Result<()> {
|
pub fn setup_lvm(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
self.header("Setting up LVM volumes")?;
|
self.header("Setting up LVM volumes")?;
|
||||||
self.cmd_list(LVM_COMMANDS, &[])
|
self.cmd_list(LVM_COMMANDS, &[])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup_boot(&self) -> Result<()> {
|
pub fn setup_boot(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
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))?;
|
||||||
@@ -341,11 +325,11 @@ impl Installer {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_syslinux(&self) -> Result<()> {
|
fn setup_syslinux(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
self.header("Installing syslinux")?;
|
self.header("Installing syslinux")?;
|
||||||
let syslinux_src = self.artifact_path("syslinux");
|
let syslinux_src = self.artifact_path("syslinux");
|
||||||
if !syslinux_src.exists() {
|
if !syslinux_src.exists() {
|
||||||
bail!("no syslinux directory found in artifact directory, cannot install syslinux");
|
panic!("no syslinux directory found in artifact directory, cannot install syslinux");
|
||||||
}
|
}
|
||||||
let dst = Path::new(INSTALL_MOUNT).join("syslinux");
|
let dst = Path::new(INSTALL_MOUNT).join("syslinux");
|
||||||
util::create_dir(&dst)?;
|
util::create_dir(&dst)?;
|
||||||
@@ -363,17 +347,17 @@ impl Installer {
|
|||||||
self.cmd(format!("/sbin/extlinux --install {}", dst.display()))
|
self.cmd(format!("/sbin/extlinux --install {}", dst.display()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_syslinux_post_umount(&self) -> Result<()> {
|
fn setup_syslinux_post_umount(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mbrbin = self.artifact_path("syslinux/gptmbr.bin");
|
let mbrbin = self.artifact_path("syslinux/gptmbr.bin");
|
||||||
if !mbrbin.exists() {
|
if !mbrbin.exists() {
|
||||||
bail!("could not find MBR image: {:?}", mbrbin);
|
panic!("could not find MBR image: {:?}", mbrbin);
|
||||||
}
|
}
|
||||||
self.cmd(format!("/bin/dd bs=440 count=1 conv=notrunc if={} of={}", mbrbin.display(), self.target().display()))?;
|
self.cmd(format!("/bin/dd bs=440 count=1 conv=notrunc if={} of={}", mbrbin.display(), self.target().display()))?;
|
||||||
self.cmd(format!("/sbin/parted -s {} set 1 legacy_boot on", self.target_str()))
|
self.cmd(format!("/sbin/parted -s {} set 1 legacy_boot on", self.target_str()))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_storage(&self) -> Result<()> {
|
pub fn create_storage(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
self.header("Setting up /storage partition")?;
|
self.header("Setting up /storage partition")?;
|
||||||
|
|
||||||
self.cmd_list(CREATE_STORAGE_COMMANDS,
|
self.cmd_list(CREATE_STORAGE_COMMANDS,
|
||||||
@@ -383,7 +367,7 @@ impl Installer {
|
|||||||
self.cmd(format!("/bin/umount {}", INSTALL_MOUNT))
|
self.cmd(format!("/bin/umount {}", INSTALL_MOUNT))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_storage(&self) -> Result<()> {
|
fn setup_storage(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
if self._type == InstallType::Install {
|
if self._type == InstallType::Install {
|
||||||
self.create_keyring()?;
|
self.create_keyring()?;
|
||||||
self.setup_storage_resources()?;
|
self.setup_storage_resources()?;
|
||||||
@@ -407,33 +391,33 @@ impl Installer {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_keyring(&self) -> Result<()> {
|
fn create_keyring(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
self.info("Creating initial keyring")?;
|
self.info("Creating initial keyring")?;
|
||||||
let keyring = KeyRing::create_new();
|
let keyring = KeyRing::create_new();
|
||||||
keyring.write(self.storage().join("keyring"), self.passphrase.as_ref().unwrap())
|
Ok(keyring.write(self.storage().join("keyring"), self.passphrase.as_ref().unwrap())?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn setup_base_realmfs(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
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)?;
|
||||||
self.sparse_copy_artifact("base-realmfs.img", &realmfs_dir)?;
|
self.sparse_copy_artifact("base-realmfs.img", &realmfs_dir)?;
|
||||||
self.cmd(format!("/usr/bin/citadel-image decompress {}/base-realmfs.img", realmfs_dir.display()))
|
self.cmd(format!("/usr/bin/citadel-image decompress {}/base-realmfs.img", realmfs_dir.display()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_realm_skel(&self) -> Result<()> {
|
fn setup_realm_skel(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let realm_skel = self.storage().join("realms/skel");
|
let realm_skel = self.storage().join("realms/skel");
|
||||||
util::create_dir(&realm_skel)?;
|
util::create_dir(&realm_skel)?;
|
||||||
util::copy_tree_with_chown(&self.skel(), &realm_skel, (1000,1000))
|
util::copy_tree_with_chown(&self.skel(), &realm_skel, (1000, 1000))?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_realmlock(&self, dir: &Path) -> Result<()> {
|
fn create_realmlock(&self, dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
fs::File::create(dir.join(".realmlock"))
|
fs::File::create(dir.join(".realmlock"))
|
||||||
.map_err(context!("failed to create {:?}/.realmlock file", dir))?;
|
.map_err(context!("failed to create {:?}/.realmlock file", dir))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_main_realm(&self) -> Result<()> {
|
fn setup_main_realm(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
self.header("Creating main realm")?;
|
self.header("Creating main realm")?;
|
||||||
|
|
||||||
let realm = self.storage().join("realms/realm-main");
|
let realm = self.storage().join("realms/realm-main");
|
||||||
@@ -458,7 +442,7 @@ impl Installer {
|
|||||||
self.create_realmlock(&realm)
|
self.create_realmlock(&realm)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_apt_cacher_realm(&self) -> Result<()> {
|
fn setup_apt_cacher_realm(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
self.header("Creating apt-cacher realm")?;
|
self.header("Creating apt-cacher realm")?;
|
||||||
let realm_base = self.storage().join("realms/realm-apt-cacher");
|
let realm_base = self.storage().join("realms/realm-apt-cacher");
|
||||||
|
|
||||||
@@ -478,7 +462,7 @@ impl Installer {
|
|||||||
self.create_realmlock(&realm_base)
|
self.create_realmlock(&realm_base)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_storage_resources(&self) -> Result<()> {
|
fn setup_storage_resources(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let channel = match OsRelease::citadel_channel() {
|
let channel = match OsRelease::citadel_channel() {
|
||||||
Some(channel) => channel,
|
Some(channel) => channel,
|
||||||
None => "dev",
|
None => "dev",
|
||||||
@@ -492,7 +476,7 @@ impl Installer {
|
|||||||
self.sparse_copy_artifact(&kernel_img, &resources)
|
self.sparse_copy_artifact(&kernel_img, &resources)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_citadel_passphrase(&self) -> Result<()> {
|
fn setup_citadel_passphrase(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
if self._type == InstallType::LiveSetup {
|
if self._type == InstallType::LiveSetup {
|
||||||
self.info("Creating temporary citadel passphrase file for live mode")?;
|
self.info("Creating temporary citadel passphrase file for live mode")?;
|
||||||
let path = self.storage().join("citadel-state/passwd");
|
let path = self.storage().join("citadel-state/passwd");
|
||||||
@@ -515,21 +499,15 @@ impl Installer {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn install_rootfs_partitions(&self) -> Result<()> {
|
pub fn install_rootfs_partitions(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
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()))
|
||||||
if self.timezone.is_some() {
|
|
||||||
self.setup_localtime_symlink(self.timezone.clone())?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn finish_install(&self) -> Result<()> {
|
pub fn finish_install(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
self.cmd_list(FINISH_COMMANDS, &[
|
self.cmd_list(FINISH_COMMANDS, &[("$TARGET", self.target_str())])
|
||||||
("$TARGET", self.target_str())
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn global_realm_config(&self) -> &str {
|
fn global_realm_config(&self) -> &str {
|
||||||
@@ -568,16 +546,33 @@ impl Installer {
|
|||||||
Path::new(&self.artifact_directory).join(filename)
|
Path::new(&self.artifact_directory).join(filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn copy_artifact<P: AsRef<Path>>(&self, filename: &str, target: P) -> Result<()> {
|
fn copy_artifact<P: AsRef<Path>>(
|
||||||
|
&self,
|
||||||
|
filename: &str,
|
||||||
|
target: P,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
self._copy_artifact(filename, target, false)
|
self._copy_artifact(filename, target, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sparse_copy_artifact<P: AsRef<Path>>(&self, filename: &str, target: P) -> Result<()> {
|
fn sparse_copy_artifact<P: AsRef<Path>>(
|
||||||
|
&self,
|
||||||
|
filename: &str,
|
||||||
|
target: P,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
self._copy_artifact(filename, target, true)
|
self._copy_artifact(filename, target, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _copy_artifact<P: AsRef<Path>>(&self, filename: &str, target: P, sparse: bool) -> Result<()> {
|
fn _copy_artifact<P: AsRef<Path>>(
|
||||||
self.info(format!("Copying {} to {}", filename, target.as_ref().display()))?;
|
&self,
|
||||||
|
filename: &str,
|
||||||
|
target: P,
|
||||||
|
sparse: bool,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
self.info(format!(
|
||||||
|
"Copying {} to {}",
|
||||||
|
filename,
|
||||||
|
target.as_ref().display()
|
||||||
|
))?;
|
||||||
let src = self.artifact_path(filename);
|
let src = self.artifact_path(filename);
|
||||||
let target = target.as_ref();
|
let target = target.as_ref();
|
||||||
util::create_dir(target)?;
|
util::create_dir(target)?;
|
||||||
@@ -590,20 +585,19 @@ impl Installer {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn header<S: AsRef<str>>(&self, s: S) -> Result<()> {
|
fn header<S: AsRef<str>>(&self, s: S) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
self.output(format!("\n[+] {}\n", s.as_ref()))
|
self.output(format!("\n[+] {}\n", s.as_ref()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn info<S: AsRef<str>>(&self, s: S) -> Result<()> {
|
fn info<S: AsRef<str>>(&self, s: S) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
self.output(format!(" [>] {}", s.as_ref()))
|
self.output(format!(" [>] {}", s.as_ref()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn output<S: AsRef<str>>(&self, s: S) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
fn output<S: AsRef<str>>(&self, s: S) -> Result<()> {
|
self.write_output(s.as_ref())
|
||||||
self.write_output(s.as_ref()).map_err(context!("error writing output"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_output(&self, s: &str) -> io::Result<()> {
|
fn write_output(&self, s: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
println!("{}", s);
|
println!("{}", s);
|
||||||
io::stdout().flush()?;
|
io::stdout().flush()?;
|
||||||
|
|
||||||
@@ -614,7 +608,11 @@ impl Installer {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cmd_list<I: IntoIterator<Item=S>, S: AsRef<str>>(&self, cmd_lines: I, subs: &[(&str,&str)]) -> Result<()> {
|
fn cmd_list<I: IntoIterator<Item = S>, S: AsRef<str>>(
|
||||||
|
&self,
|
||||||
|
cmd_lines: I,
|
||||||
|
subs: &[(&str, &str)],
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
for line in cmd_lines {
|
for line in cmd_lines {
|
||||||
let line = line.as_ref();
|
let line = line.as_ref();
|
||||||
let line = subs.iter().fold(line.to_string(), |acc, (from,to)| acc.replace(from,to));
|
let line = subs.iter().fold(line.to_string(), |acc, (from,to)| acc.replace(from,to));
|
||||||
@@ -624,12 +622,12 @@ impl Installer {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cmd<S: AsRef<str>>(&self, args: S) -> Result<()> {
|
fn cmd<S: AsRef<str>>(&self, args: S) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let args: Vec<&str> = args.as_ref().split_whitespace().collect::<Vec<_>>();
|
let args: Vec<&str> = args.as_ref().split_whitespace().collect::<Vec<_>>();
|
||||||
self.run_cmd(args, false)
|
self.run_cmd(args, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_cmd(&self, args: Vec<&str>, as_user: bool) -> Result<()> {
|
fn run_cmd(&self, args: Vec<&str>, as_user: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
self.output(format!(" # {}", args.join(" ")))?;
|
self.output(format!(" # {}", args.join(" ")))?;
|
||||||
|
|
||||||
let mut command = Command::new(args[0]);
|
let mut command = Command::new(args[0]);
|
||||||
@@ -654,8 +652,8 @@ impl Installer {
|
|||||||
|
|
||||||
if !result.status.success() {
|
if !result.status.success() {
|
||||||
match result.status.code() {
|
match result.status.code() {
|
||||||
Some(code) => bail!("command {} failed with exit code: {}", args[0], code),
|
Some(code) => panic!("command {} failed with exit code: {}", args[0], code),
|
||||||
None => bail!("command {} failed with no exit code", args[0]),
|
None => panic!("command {} failed with no exit code", args[0]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ pub(crate) mod installer;
|
|||||||
mod cli;
|
mod cli;
|
||||||
mod disk;
|
mod disk;
|
||||||
|
|
||||||
pub fn main(args: Vec<String>) {
|
pub fn main(args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut args = args.iter().skip(1);
|
let mut args = args.iter().skip(1);
|
||||||
let result = if let Some(dev) = args.next() {
|
let result = if let Some(dev) = args.next() {
|
||||||
cli::run_cli_install_with(dev)
|
cli::run_cli_install_with(dev)
|
||||||
@@ -17,10 +17,11 @@ pub fn main(args: Vec<String>) {
|
|||||||
Err(ref err) => {
|
Err(ref err) => {
|
||||||
println!("Install failed: {}", err);
|
println!("Install failed: {}", err);
|
||||||
exit(1);
|
exit(1);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
if !ok {
|
if !ok {
|
||||||
println!("Install cancelled...");
|
println!("Install cancelled...");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
272
citadel-tool/src/install_backend/dbus.rs
Normal file
272
citadel-tool/src/install_backend/dbus.rs
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
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 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, Box<dyn std::error::Error>> {
|
||||||
|
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<(), Box<dyn std::error::Error>> {
|
||||||
|
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<(), Box<dyn std::error::Error>> {
|
||||||
|
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) {
|
||||||
|
panic!("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 = ();
|
||||||
|
}
|
||||||
80
citadel-tool/src/install_backend/disk.rs
Normal file
80
citadel-tool/src/install_backend/disk.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +1,20 @@
|
|||||||
use anyhow::Result;
|
use std::process::exit;
|
||||||
use log::info;
|
|
||||||
use tokio::runtime::Runtime;
|
|
||||||
|
|
||||||
mod zbus;
|
mod disk;
|
||||||
|
mod dbus;
|
||||||
|
use libcitadel::CommandLine;
|
||||||
|
|
||||||
pub fn main() -> Result<()> {
|
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let rt = Runtime::new().unwrap();
|
if CommandLine::install_mode() {
|
||||||
let res = rt.block_on(zbus::start_zbus_server());
|
run_dbus_server()
|
||||||
|
} else {
|
||||||
|
println!("Citadel installer backend will only run in install or live mode");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match res {
|
fn run_dbus_server() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
Ok(_) => {
|
let server = dbus::DbusServer::connect()?;
|
||||||
info!("Starting Installer Zbus server");
|
server.start()?;
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
warn!(
|
|
||||||
"The Zbus server failed to start or encountered a fatal error: {:?}",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,161 +0,0 @@
|
|||||||
use crate::install::installer::*;
|
|
||||||
use event_listener::{Event, Listener};
|
|
||||||
use zbus::connection::Builder;
|
|
||||||
use zbus::fdo;
|
|
||||||
use zbus::interface;
|
|
||||||
use zbus::object_server::SignalEmitter;
|
|
||||||
use zbus::Result;
|
|
||||||
|
|
||||||
const OBJECT_PATH: &str = "/com/subgraph/installer";
|
|
||||||
const BUS_NAME: &str = "com.subgraph.installer";
|
|
||||||
|
|
||||||
pub struct DbusServer {
|
|
||||||
done: Event,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn start_zbus_server() -> anyhow::Result<()> {
|
|
||||||
let dbus_server = DbusServer {
|
|
||||||
done: event_listener::Event::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let done_listener = dbus_server.done.listen();
|
|
||||||
let _connection = Builder::system()?
|
|
||||||
.name(BUS_NAME)?
|
|
||||||
.serve_at(OBJECT_PATH, dbus_server)?
|
|
||||||
.build()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
done_listener.wait();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[interface(name = "com.subgraph.installer.Manager")]
|
|
||||||
impl DbusServer {
|
|
||||||
async fn run_install(
|
|
||||||
&self,
|
|
||||||
#[zbus(signal_emitter)] emitter: SignalEmitter<'_>,
|
|
||||||
device_path: &str,
|
|
||||||
citadel_passphrase: &str,
|
|
||||||
luks_passphrase: &str,
|
|
||||||
) -> fdo::Result<()> {
|
|
||||||
run_install_task(
|
|
||||||
emitter,
|
|
||||||
device_path,
|
|
||||||
citadel_passphrase,
|
|
||||||
luks_passphrase,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run_install_with_timezone(
|
|
||||||
&self,
|
|
||||||
#[zbus(signal_emitter)] emitter: SignalEmitter<'_>,
|
|
||||||
device_path: &str,
|
|
||||||
citadel_passphrase: &str,
|
|
||||||
luks_passphrase: &str,
|
|
||||||
timezone: &str,
|
|
||||||
) -> fdo::Result<()> {
|
|
||||||
run_install_task(
|
|
||||||
emitter,
|
|
||||||
device_path,
|
|
||||||
citadel_passphrase,
|
|
||||||
luks_passphrase,
|
|
||||||
Some(timezone.to_string()),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[zbus(signal)]
|
|
||||||
async fn run_install_started(emitter: &SignalEmitter<'_>, message: &str) -> Result<()>;
|
|
||||||
|
|
||||||
#[zbus(signal)]
|
|
||||||
async fn citadel_password_set(emitter: &SignalEmitter<'_>) -> Result<()>;
|
|
||||||
|
|
||||||
#[zbus(signal)]
|
|
||||||
async fn disk_partitioned(emitter: &SignalEmitter<'_>, message: &str) -> Result<()>;
|
|
||||||
|
|
||||||
#[zbus(signal)]
|
|
||||||
async fn lvm_setup(emitter: &SignalEmitter<'_>, message: &str) -> Result<()>;
|
|
||||||
|
|
||||||
#[zbus(signal)]
|
|
||||||
async fn luks_setup(emitter: &SignalEmitter<'_>, message: &str) -> Result<()>;
|
|
||||||
|
|
||||||
#[zbus(signal)]
|
|
||||||
async fn boot_setup(emitter: &SignalEmitter<'_>, message: &str) -> Result<()>;
|
|
||||||
|
|
||||||
#[zbus(signal)]
|
|
||||||
async fn storage_created(emitter: &SignalEmitter<'_>, message: &str) -> Result<()>;
|
|
||||||
|
|
||||||
#[zbus(signal)]
|
|
||||||
async fn rootfs_installed(emitter: &SignalEmitter<'_>, message: &str) -> Result<()>;
|
|
||||||
|
|
||||||
#[zbus(signal)]
|
|
||||||
async fn install_completed(emitter: &SignalEmitter<'_>) -> Result<()>;
|
|
||||||
|
|
||||||
#[zbus(signal)]
|
|
||||||
async fn install_failed(emitter: &SignalEmitter<'_>, reason: &str) -> Result<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run_install_task(
|
|
||||||
emitter: SignalEmitter<'_>,
|
|
||||||
device_path: &str,
|
|
||||||
citadel_passphrase: &str,
|
|
||||||
luks_passphrase: &str,
|
|
||||||
timezone: Option<String>, // Accepts an owned String
|
|
||||||
) -> fdo::Result<()> {
|
|
||||||
println!(
|
|
||||||
"-> Installation process started for device '{}'...",
|
|
||||||
device_path
|
|
||||||
);
|
|
||||||
let msg1 = "Installation process has begun...";
|
|
||||||
DbusServer::run_install_started(&emitter, msg1).await?;
|
|
||||||
|
|
||||||
// We convert the `Option<String>` to an `Option<&str>` for `Installer::new`
|
|
||||||
let mut install = Installer::new(device_path, citadel_passphrase, luks_passphrase, timezone);
|
|
||||||
|
|
||||||
install.set_install_syslinux(true);
|
|
||||||
install
|
|
||||||
.verify()
|
|
||||||
.map_err(|e| fdo::Error::Failed(e.to_string()))?;
|
|
||||||
install
|
|
||||||
.partition_disk()
|
|
||||||
.map_err(|e| fdo::Error::Failed(e.to_string()))?;
|
|
||||||
|
|
||||||
install
|
|
||||||
.setup_luks()
|
|
||||||
.map_err(|e| fdo::Error::Failed(e.to_string()))?;
|
|
||||||
let msg2 = "Setup LUKS disk encryption password successfully\n";
|
|
||||||
DbusServer::luks_setup(&emitter, msg2).await?;
|
|
||||||
|
|
||||||
install
|
|
||||||
.setup_lvm()
|
|
||||||
.map_err(|e| fdo::Error::Failed(e.to_string()))?;
|
|
||||||
let msg3 = "Disk has been partitioned successfully.";
|
|
||||||
DbusServer::lvm_setup(&emitter, msg3).await?;
|
|
||||||
|
|
||||||
install
|
|
||||||
.setup_boot()
|
|
||||||
.map_err(|e| fdo::Error::Failed(e.to_string()))?;
|
|
||||||
let msg4 = "Setup /boot partition successfully\n";
|
|
||||||
DbusServer::boot_setup(&emitter, msg4).await?;
|
|
||||||
|
|
||||||
install
|
|
||||||
.create_storage()
|
|
||||||
.map_err(|e| fdo::Error::Failed(e.to_string()))?;
|
|
||||||
let msg5 = "Setup /storage partition successfully\n";
|
|
||||||
DbusServer::storage_created(&emitter, msg5).await?;
|
|
||||||
|
|
||||||
install
|
|
||||||
.install_rootfs_partitions()
|
|
||||||
.map_err(|e| fdo::Error::Failed(e.to_string()))?;
|
|
||||||
let msg6 = "Installed rootfs partitions successfully\n";
|
|
||||||
DbusServer::rootfs_installed(&emitter, msg6).await?;
|
|
||||||
|
|
||||||
install
|
|
||||||
.finish_install()
|
|
||||||
.map_err(|e| fdo::Error::Failed(e.to_string()))?;
|
|
||||||
DbusServer::install_completed(&emitter).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -16,72 +16,75 @@ mod mkimage;
|
|||||||
mod realmfs;
|
mod realmfs;
|
||||||
mod sync;
|
mod sync;
|
||||||
mod update;
|
mod update;
|
||||||
mod fetch;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let exe = match env::current_exe() {
|
let exe = env::current_exe()?;
|
||||||
Ok(path) => path,
|
|
||||||
Err(_e) => {
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let args = env::args().collect::<Vec<String>>();
|
let args = env::args().collect::<Vec<String>>();
|
||||||
|
|
||||||
if exe == Path::new("/usr/libexec/citadel-boot") {
|
if exe == Path::new("/usr/libexec/citadel-boot") {
|
||||||
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") {
|
} else if exe == Path::new("/usr/libexec/citadel-install-backend") {
|
||||||
install_backend::main().unwrap();
|
install_backend::main()
|
||||||
} else if exe == Path::new("/usr/bin/citadel-image") {
|
} else if exe == Path::new("/usr/bin/citadel-image") {
|
||||||
image::main();
|
image::main(args)
|
||||||
} else if exe == Path::new("/usr/bin/citadel-realmfs") {
|
} else if exe == Path::new("/usr/bin/citadel-realmfs") {
|
||||||
realmfs::main();
|
realmfs::main(args)
|
||||||
} else if exe == Path::new("/usr/bin/citadel-update") {
|
} else if exe == Path::new("/usr/bin/citadel-update") {
|
||||||
update::main(args);
|
update::main(args)
|
||||||
} else if exe == Path::new("/usr/bin/citadel-fetch") {
|
|
||||||
fetch::main();
|
|
||||||
} else if exe == Path::new("/usr/libexec/citadel-desktop-sync") {
|
} else if exe == Path::new("/usr/libexec/citadel-desktop-sync") {
|
||||||
sync::main(args);
|
sync::main(args)
|
||||||
} else if exe == Path::new("/usr/libexec/citadel-run") {
|
} else if exe == Path::new("/usr/libexec/citadel-run") {
|
||||||
do_citadel_run(args);
|
do_citadel_run(args)
|
||||||
} else if exe.file_name() == Some(OsStr::new("citadel-mkimage")) {
|
} else if exe.file_name() == Some(OsStr::new("citadel-mkimage")) {
|
||||||
mkimage::main(args);
|
mkimage::main(args)
|
||||||
} else if exe.file_name() == Some(OsStr::new("citadel-tool")) {
|
} else if exe.file_name() == Some(OsStr::new("citadel-tool")) {
|
||||||
dispatch_command(args);
|
dispatch_command(args)
|
||||||
} else {
|
} else {
|
||||||
println!("Error: unknown executable {}", exe.display());
|
Result::Err(format!("Error: unknown executable {}", exe.display()).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch_command(args: Vec<String>) {
|
fn dispatch_command(args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
if let Some(command) = args.get(1) {
|
if let Some(command) = args.get(1) {
|
||||||
match command.as_str() {
|
match command.as_str() {
|
||||||
"boot" => boot::main(rebuild_args("citadel-boot", args)),
|
"boot" => boot::main(rebuild_args("citadel-boot", args)),
|
||||||
"install" => install::main(rebuild_args("citadel-install", args)),
|
"install" => install::main(rebuild_args("citadel-install", args)),
|
||||||
"image" => image::main(),
|
"image" => image::main(rebuild_args("citadel-image", args)),
|
||||||
"realmfs" => realmfs::main(),
|
"realmfs" => realmfs::main(rebuild_args("citadel-realmfs", args)),
|
||||||
"update" => update::main(rebuild_args("citadel-update", args)),
|
"update" => update::main(rebuild_args("citadel-update", args)),
|
||||||
"mkimage" => mkimage::main(rebuild_args("citadel-mkimage", args)),
|
"mkimage" => mkimage::main(rebuild_args("citadel-mkimage", args)),
|
||||||
"sync" => sync::main(rebuild_args("citadel-desktop-sync", args)),
|
"sync" => sync::main(rebuild_args("citadel-desktop-sync", args)),
|
||||||
"run" => do_citadel_run(rebuild_args("citadel-run", args)),
|
"run" => do_citadel_run(rebuild_args("citadel-run", args)),
|
||||||
_ => println!("Error: unknown command {}", command),
|
_ => throw_err(command),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("Must provide an argument");
|
Result::Err("Must provide an argument".into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn throw_err(command: &String) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
Result::Err(format!("Error: unknown command {}", command).into())
|
||||||
|
}
|
||||||
|
|
||||||
fn rebuild_args(command: &str, args: Vec<String>) -> Vec<String> {
|
fn rebuild_args(command: &str, args: Vec<String>) -> Vec<String> {
|
||||||
iter::once(command.to_string())
|
iter::once(command.to_string())
|
||||||
.chain(args.into_iter().skip(2))
|
.chain(args.into_iter().skip(2))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_citadel_run(args: Vec<String>) {
|
fn do_citadel_run(args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
if let Err(e) = RealmManager::run_in_current(&args[1..], true) {
|
if let Err(e) = RealmManager::run_in_current(&args[1..], true) {
|
||||||
println!("RealmManager::run_in_current({:?}) failed: {}", &args[1..], e);
|
return Result::Err(
|
||||||
|
format!(
|
||||||
|
"RealmManager::run_in_current({:?}) failed: {}",
|
||||||
|
&args[1..],
|
||||||
|
e
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::fs::OpenOptions;
|
|||||||
use std::fs::{self,File};
|
use std::fs::{self,File};
|
||||||
use std::io::{self,Write};
|
use std::io::{self,Write};
|
||||||
|
|
||||||
use libcitadel::{Result, ImageHeader, devkeys, util, keypair_for_channel_signing};
|
use libcitadel::{Result, ImageHeader, devkeys, util};
|
||||||
|
|
||||||
use super::config::BuildConfig;
|
use super::config::BuildConfig;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -38,15 +38,15 @@ impl UpdateBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn target_filename(&self) -> String {
|
fn target_filename(&self) -> String {
|
||||||
format!("citadel-{}-{}-{}.img", self.config.img_name(), self.config.channel(), self.config.version())
|
format!("citadel-{}-{}-{:03}.img", self.config.img_name(), self.config.channel(), self.config.version())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_filename(config: &BuildConfig) -> String {
|
fn build_filename(config: &BuildConfig) -> String {
|
||||||
format!("citadel-{}-{}-{}", config.image_type(), config.channel(), config.version())
|
format!("citadel-{}-{}-{:03}", config.image_type(), config.channel(), config.version())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verity_filename(&self) -> String {
|
fn verity_filename(&self) -> String {
|
||||||
format!("verity-hash-{}-{}", self.config.image_type(), self.config.version())
|
format!("verity-hash-{}-{:03}", self.config.image_type(), self.config.version())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(&mut self) -> Result<()> {
|
pub fn build(&mut self) -> Result<()> {
|
||||||
@@ -154,7 +154,7 @@ impl UpdateBuilder {
|
|||||||
bail!("failed to compress {:?}: {}", self.image(), err);
|
bail!("failed to compress {:?}: {}", self.image(), err);
|
||||||
}
|
}
|
||||||
// Rename back to original image_data filename
|
// Rename back to original image_data filename
|
||||||
util::rename(util::append_to_path(self.image(), ".xz"), self.image())?;
|
util::rename(self.image().with_extension("xz"), self.image())?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -192,19 +192,6 @@ impl UpdateBuilder {
|
|||||||
if self.config.channel() == "dev" {
|
if self.config.channel() == "dev" {
|
||||||
let sig = devkeys().sign(&metainfo);
|
let sig = devkeys().sign(&metainfo);
|
||||||
hdr.set_signature(sig.to_bytes());
|
hdr.set_signature(sig.to_bytes());
|
||||||
} else {
|
|
||||||
let private_key_path_str = match self.config.private_key_path() {
|
|
||||||
Some(path) => path,
|
|
||||||
None => bail!("private-key-path not found in config for non-dev channel"),
|
|
||||||
};
|
|
||||||
let private_key_path = Path::new(private_key_path_str);
|
|
||||||
let sig = keypair_for_channel_signing(private_key_path).sign(&metainfo);
|
|
||||||
info!("Generated signature: {}", hex::encode(sig.to_bytes()));
|
|
||||||
let generated_signature_bytes = sig.to_bytes();
|
|
||||||
if generated_signature_bytes.iter().all(|&b| b == 0) {
|
|
||||||
bail!("Generated signature is all zeros. Signing failed!");
|
|
||||||
}
|
|
||||||
hdr.set_signature(generated_signature_bytes);
|
|
||||||
}
|
}
|
||||||
Ok(hdr)
|
Ok(hdr)
|
||||||
}
|
}
|
||||||
@@ -230,7 +217,7 @@ impl UpdateBuilder {
|
|||||||
writeln!(v, "realmfs-name = \"{}\"", name)?;
|
writeln!(v, "realmfs-name = \"{}\"", name)?;
|
||||||
}
|
}
|
||||||
writeln!(v, "channel = \"{}\"", self.config.channel())?;
|
writeln!(v, "channel = \"{}\"", self.config.channel())?;
|
||||||
writeln!(v, "version = \"{}\"", self.config.version())?;
|
writeln!(v, "version = {}", self.config.version())?;
|
||||||
writeln!(v, "timestamp = \"{}\"", self.config.timestamp())?;
|
writeln!(v, "timestamp = \"{}\"", self.config.timestamp())?;
|
||||||
writeln!(v, "nblocks = {}", self.nblocks.unwrap())?;
|
writeln!(v, "nblocks = {}", self.nblocks.unwrap())?;
|
||||||
writeln!(v, "shasum = \"{}\"", self.shasum.as_ref().unwrap())?;
|
writeln!(v, "shasum = \"{}\"", self.shasum.as_ref().unwrap())?;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ pub struct BuildConfig {
|
|||||||
#[serde(rename = "image-type")]
|
#[serde(rename = "image-type")]
|
||||||
image_type: String,
|
image_type: String,
|
||||||
channel: String,
|
channel: String,
|
||||||
version: String,
|
version: usize,
|
||||||
timestamp: String,
|
timestamp: String,
|
||||||
source: String,
|
source: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -22,9 +22,6 @@ pub struct BuildConfig {
|
|||||||
#[serde(rename = "realmfs-name")]
|
#[serde(rename = "realmfs-name")]
|
||||||
realmfs_name: Option<String>,
|
realmfs_name: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "private-key-path")]
|
|
||||||
private_key_path: Option<String>,
|
|
||||||
|
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
basedir: PathBuf,
|
basedir: PathBuf,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
@@ -105,8 +102,8 @@ impl BuildConfig {
|
|||||||
self.realmfs_name.as_ref().map(|s| s.as_str())
|
self.realmfs_name.as_ref().map(|s| s.as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn version(&self) -> &str {
|
pub fn version(&self) -> usize {
|
||||||
&self.version
|
self.version
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn channel(&self) -> &str {
|
pub fn channel(&self) -> &str {
|
||||||
@@ -120,8 +117,4 @@ impl BuildConfig {
|
|||||||
pub fn compress(&self) -> bool {
|
pub fn compress(&self) -> bool {
|
||||||
self.compress
|
self.compress
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn private_key_path(&self) -> Option<&str> {
|
|
||||||
self.private_key_path.as_ref().map(|s| s.as_str())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
|
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
use libcitadel::Result;
|
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
mod build;
|
mod build;
|
||||||
|
|
||||||
pub fn main(args: Vec<String>) {
|
pub fn main(args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
||||||
let config_path = match args.get(1) {
|
let config_path = match args.get(1) {
|
||||||
Some(arg) => arg,
|
Some(arg) => arg,
|
||||||
None => {
|
None => {
|
||||||
@@ -21,11 +18,11 @@ pub fn main(args: Vec<String>) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_image(config_path: &str) -> Result<()> {
|
fn build_image(config_path: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let conf = config::BuildConfig::load(config_path)?;
|
let conf = config::BuildConfig::load(config_path)?;
|
||||||
let mut builder = build::UpdateBuilder::new(conf);
|
let mut builder = build::UpdateBuilder::new(conf);
|
||||||
builder.build()
|
Ok(builder.build()?)
|
||||||
}
|
}
|
||||||
@@ -1,24 +1,26 @@
|
|||||||
use clap::{command, Command};
|
use clap::App;
|
||||||
use clap::{Arg, ArgMatches};
|
use clap::ArgMatches;
|
||||||
|
|
||||||
use libcitadel::{Result,RealmFS,Logger,LogLevel};
|
use libcitadel::{RealmFS, Logger, LogLevel};
|
||||||
use libcitadel::util::is_euid_root;
|
use libcitadel::util::is_euid_root;
|
||||||
|
use clap::SubCommand;
|
||||||
|
use clap::AppSettings::*;
|
||||||
|
use clap::Arg;
|
||||||
use libcitadel::ResizeSize;
|
use libcitadel::ResizeSize;
|
||||||
use std::process::exit;
|
|
||||||
|
|
||||||
pub fn main() {
|
|
||||||
|
|
||||||
|
pub fn main(args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
Logger::set_log_level(LogLevel::Debug);
|
Logger::set_log_level(LogLevel::Debug);
|
||||||
|
|
||||||
let matches = command!()
|
let app = App::new("citadel-realmfs")
|
||||||
.about("citadel-realmfs")
|
.about("Citadel realmfs image tool")
|
||||||
.arg_required_else_help(true)
|
.settings(&[ArgRequiredElseHelp,ColoredHelp, DisableHelpSubcommand, DisableVersion, DeriveDisplayOrder,SubcommandsNegateReqs])
|
||||||
.subcommand(Command::new("resize")
|
|
||||||
|
.subcommand(SubCommand::with_name("resize")
|
||||||
.about("Resize an existing RealmFS image. If the image is currently sealed, it will also be unsealed.")
|
.about("Resize an existing RealmFS image. If the image is currently sealed, it will also be unsealed.")
|
||||||
.arg(Arg::new("image")
|
.arg(Arg::with_name("image")
|
||||||
.help("Path or name of RealmFS image to resize")
|
.help("Path or name of RealmFS image to resize")
|
||||||
.required(true))
|
.required(true))
|
||||||
.arg(Arg::new("size")
|
.arg(Arg::with_name("size")
|
||||||
.help("Size to increase RealmFS image to (or by if prefixed with '+')")
|
.help("Size to increase RealmFS image to (or by if prefixed with '+')")
|
||||||
.long_help("\
|
.long_help("\
|
||||||
The size can be followed by a 'g' or 'm' character \
|
The size can be followed by a 'g' or 'm' character \
|
||||||
@@ -31,66 +33,63 @@ is the final absolute size of the image.")
|
|||||||
.required(true)))
|
.required(true)))
|
||||||
|
|
||||||
|
|
||||||
.subcommand(Command::new("fork")
|
.subcommand(SubCommand::with_name("fork")
|
||||||
.about("Create a new RealmFS image as an unsealed copy of an existing image")
|
.about("Create a new RealmFS image as an unsealed copy of an existing image")
|
||||||
.arg(Arg::new("image")
|
.arg(Arg::with_name("image")
|
||||||
.help("Path or name of RealmFS image to fork")
|
.help("Path or name of RealmFS image to fork")
|
||||||
.required(true))
|
.required(true))
|
||||||
|
|
||||||
.arg(Arg::new("forkname")
|
.arg(Arg::with_name("forkname")
|
||||||
.help("Name of new image to create")
|
.help("Name of new image to create")
|
||||||
.required(true)))
|
.required(true)))
|
||||||
|
|
||||||
.subcommand(Command::new("autoresize")
|
.subcommand(SubCommand::with_name("autoresize")
|
||||||
.about("Increase size of RealmFS image if not enough free space remains")
|
.about("Increase size of RealmFS image if not enough free space remains")
|
||||||
.arg(Arg::new("image")
|
.arg(Arg::with_name("image")
|
||||||
.help("Path or name of RealmFS image")
|
.help("Path or name of RealmFS image")
|
||||||
.required(true)))
|
.required(true)))
|
||||||
|
|
||||||
.subcommand(Command::new("update")
|
.subcommand(SubCommand::with_name("update")
|
||||||
.about("Open an update shell on the image")
|
.about("Open an update shell on the image")
|
||||||
.arg(Arg::new("image")
|
.arg(Arg::with_name("image")
|
||||||
.help("Path or name of RealmFS image")
|
.help("Path or name of RealmFS image")
|
||||||
.required(true)))
|
.required(true)))
|
||||||
|
|
||||||
.subcommand(Command::new("activate")
|
.subcommand(SubCommand::with_name("activate")
|
||||||
.about("Activate a RealmFS by creating a block device for the image and mounting it.")
|
.about("Activate a RealmFS by creating a block device for the image and mounting it.")
|
||||||
.arg(Arg::new("image")
|
.arg(Arg::with_name("image")
|
||||||
.help("Path or name of RealmFS image to activate")
|
.help("Path or name of RealmFS image to activate")
|
||||||
.required(true)))
|
.required(true)))
|
||||||
|
|
||||||
.subcommand(Command::new("deactivate")
|
.subcommand(SubCommand::with_name("deactivate")
|
||||||
.about("Deactivate a RealmFS by unmounting it and removing block device created during activation.")
|
.about("Deactivate a RealmFS by unmounting it and removing block device created during activation.")
|
||||||
.arg(Arg::new("image")
|
.arg(Arg::with_name("image")
|
||||||
.help("Path or name of RealmFS image to deactivate")
|
.help("Path or name of RealmFS image to deactivate")
|
||||||
.required(true)))
|
.required(true)))
|
||||||
|
|
||||||
|
|
||||||
.arg(Arg::new("image")
|
.arg(Arg::with_name("image")
|
||||||
.help("Name of or path to RealmFS image to display information about")
|
.help("Name of or path to RealmFS image to display information about")
|
||||||
.required(true))
|
.required(true));
|
||||||
.get_matches();
|
|
||||||
|
|
||||||
|
let matches = app.get_matches_from(args);
|
||||||
let result = match matches.subcommand() {
|
let result = match matches.subcommand() {
|
||||||
Some(("resize", m)) => resize(m),
|
("resize", Some(m)) => resize(m),
|
||||||
Some(("autoresize", m)) => autoresize(m),
|
("autoresize", Some(m)) => autoresize(m),
|
||||||
Some(("fork", m)) => fork(m),
|
("fork", Some(m)) => fork(m),
|
||||||
Some(("update", m)) => update(m),
|
("update", Some(m)) => update(m),
|
||||||
Some(("activate", m)) => activate(m),
|
("activate", Some(m)) => activate(m),
|
||||||
Some(("deactivate", m)) => deactivate(m),
|
("deactivate", Some(m)) => deactivate(m),
|
||||||
_ => image_info(&matches),
|
_ => image_info(&matches),
|
||||||
};
|
}?;
|
||||||
|
|
||||||
if let Err(ref e) = result {
|
Ok(())
|
||||||
eprintln!("Error: {}", e);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realmfs_image(arg_matches: &ArgMatches) -> Result<RealmFS> {
|
fn realmfs_image(arg_matches: &ArgMatches) -> Result<RealmFS, Box<dyn std::error::Error>> {
|
||||||
let image = match arg_matches.get_one::<String>("image") {
|
let image = match arg_matches.value_of("image") {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => bail!("Image argument required."),
|
None => panic!("Image argument required."),
|
||||||
};
|
};
|
||||||
|
|
||||||
let realmfs = if RealmFS::is_valid_name(image) {
|
let realmfs = if RealmFS::is_valid_name(image) {
|
||||||
@@ -98,41 +97,45 @@ fn realmfs_image(arg_matches: &ArgMatches) -> Result<RealmFS> {
|
|||||||
} else if RealmFS::is_valid_realmfs_image(image) {
|
} else if RealmFS::is_valid_realmfs_image(image) {
|
||||||
RealmFS::load_from_path(image)?
|
RealmFS::load_from_path(image)?
|
||||||
} else {
|
} else {
|
||||||
bail!("Not a valid realmfs name or path to realmfs image file: {}", image);
|
panic!(
|
||||||
|
"Not a valid realmfs name or path to realmfs image file: {}",
|
||||||
|
image
|
||||||
|
);
|
||||||
};
|
};
|
||||||
Ok(realmfs)
|
Ok(realmfs)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn image_info(arg_matches: &ArgMatches) -> Result<()> {
|
fn image_info(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let img = realmfs_image(arg_matches)?;
|
let img = realmfs_image(arg_matches)?;
|
||||||
print!("{}", String::from_utf8(img.header().metainfo_bytes())?);
|
print!("{}", String::from_utf8(img.header().metainfo_bytes())?);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_resize_size(s: &str) -> Result<ResizeSize> {
|
fn parse_resize_size(s: &str) -> Result<ResizeSize, Box<dyn std::error::Error>> {
|
||||||
let unit = s.chars().last().filter(|c| c.is_alphabetic());
|
let unit = s.chars().last().filter(|c| c.is_alphabetic());
|
||||||
|
|
||||||
let skip = if s.starts_with('+') { 1 } else { 0 };
|
let skip = if s.starts_with('+') { 1 } else { 0 };
|
||||||
let size = s.chars()
|
let size = s
|
||||||
|
.chars()
|
||||||
.skip(skip)
|
.skip(skip)
|
||||||
.take_while(|c| c.is_numeric())
|
.take_while(|c| c.is_numeric())
|
||||||
.collect::<String>()
|
.collect::<String>()
|
||||||
.parse::<usize>()
|
.parse::<usize>()
|
||||||
.map_err(|_| format_err!("Unable to parse size value '{}'",s))?;
|
.map_err(|_| format_err!("Unable to parse size value '{}'", s))?;
|
||||||
|
|
||||||
let sz = match unit {
|
let sz = match unit {
|
||||||
Some('g') | Some('G') => ResizeSize::gigs(size),
|
Some('g') | Some('G') => ResizeSize::gigs(size),
|
||||||
Some('m') | Some('M') => ResizeSize::megs(size),
|
Some('m') | Some('M') => ResizeSize::megs(size),
|
||||||
Some(c) => bail!("Unknown size unit '{}'", c),
|
Some(c) => panic!("Unknown size unit '{}'", c),
|
||||||
None => ResizeSize::blocks(size),
|
None => ResizeSize::blocks(size),
|
||||||
};
|
};
|
||||||
Ok(sz)
|
Ok(sz)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resize(arg_matches: &ArgMatches) -> Result<()> {
|
fn resize(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let img = realmfs_image(arg_matches)?;
|
let img = realmfs_image(arg_matches)?;
|
||||||
info!("image is {}", img.path().display());
|
info!("image is {}", img.path().display());
|
||||||
let size_arg = match arg_matches.get_one::<String>("size") {
|
let size_arg = match arg_matches.value_of("size") {
|
||||||
Some(size) => size,
|
Some(size) => size,
|
||||||
None => "No size argument",
|
None => "No size argument",
|
||||||
};
|
};
|
||||||
@@ -141,51 +144,55 @@ fn resize(arg_matches: &ArgMatches) -> Result<()> {
|
|||||||
let size = parse_resize_size(size_arg)?;
|
let size = parse_resize_size(size_arg)?;
|
||||||
|
|
||||||
if mode_add {
|
if mode_add {
|
||||||
img.resize_grow_by(size)
|
img.resize_grow_by(size)?;
|
||||||
} else {
|
} else {
|
||||||
img.resize_grow_to(size)
|
img.resize_grow_to(size)?;
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn autoresize(arg_matches: &ArgMatches) -> Result<()> {
|
fn autoresize(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let img = realmfs_image(arg_matches)?;
|
let img = realmfs_image(arg_matches)?;
|
||||||
|
|
||||||
if let Some(size) = img.auto_resize_size() {
|
if let Some(size) = img.auto_resize_size() {
|
||||||
img.resize_grow_to(size)
|
img.resize_grow_to(size)?;
|
||||||
} else {
|
} else {
|
||||||
info!("RealmFS image {} has sufficient free space, doing nothing", img.path().display());
|
info!(
|
||||||
Ok(())
|
"RealmFS image {} has sufficient free space, doing nothing",
|
||||||
|
img.path().display()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fork(arg_matches: &ArgMatches) -> Result<()> {
|
fn fork(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let img = realmfs_image(arg_matches)?;
|
let img = realmfs_image(arg_matches)?;
|
||||||
let forkname = match arg_matches.get_one::<String>("forkname") {
|
let forkname = match arg_matches.value_of("forkname") {
|
||||||
Some(name) => name,
|
Some(name) => name,
|
||||||
None => bail!("No fork name argument"),
|
None => panic!("No fork name argument"),
|
||||||
};
|
};
|
||||||
if !RealmFS::is_valid_name(forkname) {
|
if !RealmFS::is_valid_name(forkname) {
|
||||||
bail!("Not a valid RealmFS image name '{}'", forkname);
|
panic!("Not a valid RealmFS image name '{}'", forkname);
|
||||||
}
|
}
|
||||||
if RealmFS::named_image_exists(forkname) {
|
if RealmFS::named_image_exists(forkname) {
|
||||||
bail!("A RealmFS image named '{}' already exists", forkname);
|
panic!("A RealmFS image named '{}' already exists", forkname);
|
||||||
}
|
}
|
||||||
img.fork(forkname)?;
|
img.fork(forkname)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(arg_matches: &ArgMatches) -> Result<()> {
|
fn update(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
if !is_euid_root() {
|
if !is_euid_root() {
|
||||||
bail!("RealmFS updates must be run as root");
|
panic!("RealmFS updates must be run as root");
|
||||||
}
|
}
|
||||||
let img = realmfs_image(arg_matches)?;
|
let img = realmfs_image(arg_matches)?;
|
||||||
img.interactive_update(Some("icy"))?;
|
img.interactive_update(Some("icy"))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn activate(arg_matches: &ArgMatches) -> Result<()> {
|
fn activate(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let img = realmfs_image(arg_matches)?;
|
let img = realmfs_image(arg_matches)?;
|
||||||
let img_arg = arg_matches.get_one::<String>("image").unwrap();
|
let img_arg = arg_matches.value_of("image").unwrap();
|
||||||
|
|
||||||
if img.is_activated() {
|
if img.is_activated() {
|
||||||
info!("RealmFS image {} is already activated", img_arg);
|
info!("RealmFS image {} is already activated", img_arg);
|
||||||
@@ -196,9 +203,9 @@ fn activate(arg_matches: &ArgMatches) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deactivate(arg_matches: &ArgMatches) -> Result<()> {
|
fn deactivate(arg_matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let img = realmfs_image(arg_matches)?;
|
let img = realmfs_image(arg_matches)?;
|
||||||
let img_arg = arg_matches.get_one::<String>("image").unwrap();
|
let img_arg = arg_matches.value_of("image").unwrap();
|
||||||
if !img.is_activated() {
|
if !img.is_activated() {
|
||||||
info!("RealmFS image {} is not activated", img_arg);
|
info!("RealmFS image {} is not activated", img_arg);
|
||||||
} else if img.is_in_use() {
|
} else if img.is_in_use() {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::path::{Path,PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use libcitadel::{Realm, Realms, Result, util};
|
use libcitadel::{Realm, Realms, util};
|
||||||
use crate::sync::parser::DesktopFileParser;
|
use crate::sync::parser::DesktopFileParser;
|
||||||
use std::fs::DirEntry;
|
use std::fs::DirEntry;
|
||||||
use crate::sync::desktop_file::DesktopFile;
|
use crate::sync::desktop_file::DesktopFile;
|
||||||
@@ -17,14 +17,13 @@ pub struct DesktopFileSync {
|
|||||||
icons: Option<IconSync>,
|
icons: Option<IconSync>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Eq,PartialEq,Hash)]
|
#[derive(Eq, PartialEq, Hash)]
|
||||||
struct DesktopItem {
|
struct DesktopItem {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
mtime: SystemTime,
|
mtime: SystemTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DesktopItem {
|
impl DesktopItem {
|
||||||
|
|
||||||
fn new(path: PathBuf, mtime: SystemTime) -> Self {
|
fn new(path: PathBuf, mtime: SystemTime) -> Self {
|
||||||
DesktopItem { path, mtime }
|
DesktopItem { path, mtime }
|
||||||
}
|
}
|
||||||
@@ -46,7 +45,7 @@ impl DesktopItem {
|
|||||||
impl DesktopFileSync {
|
impl DesktopFileSync {
|
||||||
pub const CITADEL_APPLICATIONS: &'static str = "/home/citadel/.local/share/applications";
|
pub const CITADEL_APPLICATIONS: &'static str = "/home/citadel/.local/share/applications";
|
||||||
|
|
||||||
pub fn sync_active_realms() -> Result<()> {
|
pub fn sync_active_realms() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let realms = Realms::load()?;
|
let realms = Realms::load()?;
|
||||||
for realm in realms.active(true) {
|
for realm in realms.active(true) {
|
||||||
let mut sync = DesktopFileSync::new(realm);
|
let mut sync = DesktopFileSync::new(realm);
|
||||||
@@ -72,7 +71,7 @@ impl DesktopFileSync {
|
|||||||
DesktopFileSync { realm, items: HashSet::new(), icons }
|
DesktopFileSync { realm, items: HashSet::new(), icons }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_sync(&mut self, clear: bool) -> Result<()> {
|
pub fn run_sync(&mut self, clear: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
||||||
IconSync::ensure_theme_index_exists()?;
|
IconSync::ensure_theme_index_exists()?;
|
||||||
|
|
||||||
@@ -98,8 +97,8 @@ impl DesktopFileSync {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_source_files(&mut self, directory: impl AsRef<Path>) -> Result<()> {
|
fn collect_source_files(&mut self, directory: impl AsRef<Path>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut directory = self.realm.run_path().join(directory.as_ref());
|
let mut directory = Realms::current_realm_symlink().join(directory.as_ref());
|
||||||
directory.push("share/applications");
|
directory.push("share/applications");
|
||||||
if directory.exists() {
|
if directory.exists() {
|
||||||
util::read_directory(&directory, |dent| {
|
util::read_directory(&directory, |dent| {
|
||||||
@@ -119,20 +118,16 @@ impl DesktopFileSync {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_target_files() -> Result<()> {
|
pub fn clear_target_files() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
util::read_directory(Self::CITADEL_APPLICATIONS, |dent| {
|
Ok(util::read_directory(Self::CITADEL_APPLICATIONS, |dent| {
|
||||||
util::remove_file(dent.path())
|
util::remove_file(dent.path())
|
||||||
})
|
})?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_missing_target_files(&mut self) -> Result<()> {
|
fn remove_missing_target_files(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut sources = self.source_filenames();
|
let sources = self.source_filenames();
|
||||||
// If flatpak is enabled, don't remove the generated GNOME Software desktop file
|
|
||||||
if self.realm.config().flatpak() {
|
|
||||||
sources.insert(format!("realm-{}.org.gnome.Software.desktop", self.realm.name()));
|
|
||||||
}
|
|
||||||
let prefix = format!("realm-{}.", self.realm.name());
|
let prefix = format!("realm-{}.", self.realm.name());
|
||||||
util::read_directory(Self::CITADEL_APPLICATIONS, |dent| {
|
Ok(util::read_directory(Self::CITADEL_APPLICATIONS, |dent| {
|
||||||
if let Some(filename) = dent.file_name().to_str() {
|
if let Some(filename) = dent.file_name().to_str() {
|
||||||
if filename.starts_with(&prefix) && !sources.contains(filename) {
|
if filename.starts_with(&prefix) && !sources.contains(filename) {
|
||||||
let path = dent.path();
|
let path = dent.path();
|
||||||
@@ -141,7 +136,7 @@ impl DesktopFileSync {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mtime(path: &Path) -> Option<SystemTime> {
|
fn mtime(path: &Path) -> Option<SystemTime> {
|
||||||
@@ -160,7 +155,7 @@ impl DesktopFileSync {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn synchronize_items(&self) -> Result<()> {
|
fn synchronize_items(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
for item in &self.items {
|
for item in &self.items {
|
||||||
let target = Path::new(Self::CITADEL_APPLICATIONS).join(item.filename());
|
let target = Path::new(Self::CITADEL_APPLICATIONS).join(item.filename());
|
||||||
if item.is_newer_than(&target) {
|
if item.is_newer_than(&target) {
|
||||||
@@ -184,11 +179,9 @@ impl DesktopFileSync {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sync_item(&self, item: &DesktopItem) -> Result<()> {
|
fn sync_item(&self, item: &DesktopItem) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut dfp = DesktopFileParser::parse_from_path(&item.path, "/usr/libexec/citadel-run ")?;
|
let mut dfp = DesktopFileParser::parse_from_path(&item.path, "/usr/libexec/citadel-run ")?;
|
||||||
// When use-flatpak is enabled a gnome-software desktop file will be generated
|
if dfp.is_showable() {
|
||||||
let flatpak_gs_hide = dfp.filename() == "org.gnome.Software.desktop" && self.realm.config().flatpak();
|
|
||||||
if dfp.is_showable() && !flatpak_gs_hide {
|
|
||||||
self.sync_item_icon(&mut dfp);
|
self.sync_item_icon(&mut dfp);
|
||||||
dfp.write_to_dir(Self::CITADEL_APPLICATIONS, Some(&self.realm))?;
|
dfp.write_to_dir(Self::CITADEL_APPLICATIONS, Some(&self.realm))?;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use libcitadel::{Result, util, Realm};
|
use libcitadel::{Result, util, Realm};
|
||||||
use std::cell::{RefCell, Cell};
|
use std::cell::{RefCell, Cell};
|
||||||
|
use std::fs;
|
||||||
use crate::sync::desktop_file::DesktopFile;
|
use crate::sync::desktop_file::DesktopFile;
|
||||||
use crate::sync::REALM_BASE_PATHS;
|
use crate::sync::REALM_BASE_PATHS;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use libcitadel::{Result, Logger, LogLevel};
|
use libcitadel::{Logger, LogLevel};
|
||||||
|
|
||||||
mod desktop_file;
|
mod desktop_file;
|
||||||
mod parser;
|
mod parser;
|
||||||
@@ -14,13 +14,12 @@ fn has_arg(args: &[String], arg: &str) -> bool {
|
|||||||
|
|
||||||
pub const REALM_BASE_PATHS:&[&str] = &[
|
pub const REALM_BASE_PATHS:&[&str] = &[
|
||||||
"rootfs/usr",
|
"rootfs/usr",
|
||||||
"flatpak/exports",
|
"rootfs/var/lib/flatpak/exports",
|
||||||
"home/.local",
|
"home/.local",
|
||||||
"home/.local/share/flatpak/exports"
|
"home/.local/share/flatpak/exports"
|
||||||
];
|
];
|
||||||
|
|
||||||
pub fn main(args: Vec<String>) {
|
pub fn main(args: Vec<String>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
||||||
if has_arg(&args, "-v") {
|
if has_arg(&args, "-v") {
|
||||||
Logger::set_log_level(LogLevel::Debug);
|
Logger::set_log_level(LogLevel::Debug);
|
||||||
} else {
|
} else {
|
||||||
@@ -37,12 +36,14 @@ pub fn main(args: Vec<String>) {
|
|||||||
println!("Desktop file sync failed: {}", e);
|
println!("Desktop file sync failed: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sync(clear: bool) -> Result<()> {
|
fn sync(clear: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
if let Some(mut sync) = DesktopFileSync::new_current() {
|
if let Some(mut sync) = DesktopFileSync::new_current() {
|
||||||
sync.run_sync(clear)
|
sync.run_sync(clear)?
|
||||||
} else {
|
} else {
|
||||||
DesktopFileSync::clear_target_files()
|
DesktopFileSync::clear_target_files()?
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use libcitadel::{Result, Partition, ResourceImage, ImageHeader, LogLevel, Logger, util};
|
use libcitadel::{Partition, ResourceImage, ImageHeader, LogLevel, Logger, util};
|
||||||
use crate::update::kernel::{KernelInstaller, KernelVersion};
|
use crate::update::kernel::{KernelInstaller, KernelVersion};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fs::{DirEntry, File};
|
use std::fs::{DirEntry, File};
|
||||||
@@ -16,7 +16,7 @@ const FLAG_QUIET: u32 = 0x04;
|
|||||||
const RESOURCES_DIRECTORY: &str = "/storage/resources";
|
const RESOURCES_DIRECTORY: &str = "/storage/resources";
|
||||||
const TEMP_DIRECTORY: &str = "/storage/resources/tmp";
|
const TEMP_DIRECTORY: &str = "/storage/resources/tmp";
|
||||||
|
|
||||||
pub fn main(args: Vec<String>) {
|
pub fn main(args: Vec<String>) -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||||
let mut args = args.iter().skip(1);
|
let mut args = args.iter().skip(1);
|
||||||
let mut flags = 0;
|
let mut flags = 0;
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ pub fn main(args: Vec<String>) {
|
|||||||
Logger::set_log_level(LogLevel::Debug);
|
Logger::set_log_level(LogLevel::Debug);
|
||||||
} else if arg == "--choose-rootfs" {
|
} else if arg == "--choose-rootfs" {
|
||||||
let _ = choose_install_partition(true);
|
let _ = choose_install_partition(true);
|
||||||
return;
|
return Ok(())
|
||||||
} else {
|
} else {
|
||||||
let path = Path::new(arg);
|
let path = Path::new(arg);
|
||||||
if let Err(e) = install_image(path, flags) {
|
if let Err(e) = install_image(path, flags) {
|
||||||
@@ -42,12 +42,13 @@ pub fn main(args: Vec<String>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search directory containing installed image files for an
|
// Search directory containing installed image files for an
|
||||||
// image file that has an identical shasum and abort the installation
|
// image file that has an identical shasum and abort the installation
|
||||||
// if a duplicate is found.
|
// if a duplicate is found.
|
||||||
fn detect_duplicates(header: &ImageHeader) -> Result<()> {
|
fn detect_duplicates(header: &ImageHeader) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let metainfo = header.metainfo();
|
let metainfo = header.metainfo();
|
||||||
let channel = metainfo.channel();
|
let channel = metainfo.channel();
|
||||||
let shasum = metainfo.shasum();
|
let shasum = metainfo.shasum();
|
||||||
@@ -61,17 +62,20 @@ fn detect_duplicates(header: &ImageHeader) -> Result<()> {
|
|||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
util::read_directory(&resource_dir, |dent| {
|
Ok(util::read_directory(&resource_dir, |dent| {
|
||||||
if let Ok(hdr) = ImageHeader::from_file(dent.path()) {
|
if let Ok(hdr) = ImageHeader::from_file(dent.path()) {
|
||||||
if hdr.metainfo().shasum() == shasum {
|
if hdr.metainfo().shasum() == shasum {
|
||||||
bail!("A duplicate image file with the same shasum already exists at {}", dent.path().display());
|
panic!(
|
||||||
|
"A duplicate image file with the same shasum already exists at {}",
|
||||||
|
dent.path().display()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_tmp_copy(path: &Path) -> Result<PathBuf> {
|
fn create_tmp_copy(path: &Path) -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||||
if !Path::new(TEMP_DIRECTORY).exists() {
|
if !Path::new(TEMP_DIRECTORY).exists() {
|
||||||
util::create_dir(TEMP_DIRECTORY)?;
|
util::create_dir(TEMP_DIRECTORY)?;
|
||||||
}
|
}
|
||||||
@@ -93,12 +97,12 @@ fn create_tmp_copy(path: &Path) -> Result<PathBuf> {
|
|||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn install_image(path: &Path, flags: u32) -> Result<()> {
|
fn install_image(path: &Path, flags: u32) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
if !path.exists() || path.file_name().is_none() {
|
if !path.exists() || path.file_name().is_none() {
|
||||||
bail!("file path {} does not exist", path.display());
|
panic!("file path {} does not exist", path.display());
|
||||||
}
|
}
|
||||||
if !util::is_euid_root() {
|
if !util::is_euid_root() {
|
||||||
bail!("Image updates must be installed by root user");
|
panic!("Image updates must be installed by root user");
|
||||||
}
|
}
|
||||||
|
|
||||||
let header = ImageHeader::from_file(path)?;
|
let header = ImageHeader::from_file(path)?;
|
||||||
@@ -114,13 +118,13 @@ pub fn install_image(path: &Path, flags: u32) -> Result<()> {
|
|||||||
"kernel" => install_kernel_image(&mut image),
|
"kernel" => install_kernel_image(&mut image),
|
||||||
"extra" => install_extra_image(&image),
|
"extra" => install_extra_image(&image),
|
||||||
"rootfs" => install_rootfs_image(&image, flags),
|
"rootfs" => install_rootfs_image(&image, flags),
|
||||||
image_type => bail!("Unknown image type: {}", image_type),
|
image_type => panic!("Unknown image type: {}", image_type),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare the image file for installation by decompressing and generating
|
// Prepare the image file for installation by decompressing and generating
|
||||||
// dmverity hash tree.
|
// dmverity hash tree.
|
||||||
fn prepare_image(image: &ResourceImage, flags: u32) -> Result<()> {
|
fn prepare_image(image: &ResourceImage, flags: u32) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
if image.is_compressed() {
|
if image.is_compressed() {
|
||||||
image.decompress(false)?;
|
image.decompress(false)?;
|
||||||
}
|
}
|
||||||
@@ -129,7 +133,7 @@ fn prepare_image(image: &ResourceImage, flags: u32) -> Result<()> {
|
|||||||
info!("Verifying sha256 hash of image");
|
info!("Verifying sha256 hash of image");
|
||||||
let shasum = image.generate_shasum()?;
|
let shasum = image.generate_shasum()?;
|
||||||
if shasum != image.metainfo().shasum() {
|
if shasum != image.metainfo().shasum() {
|
||||||
bail!("image file does not have expected sha256 value");
|
panic!("image file does not have expected sha256 value");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,24 +143,28 @@ fn prepare_image(image: &ResourceImage, flags: u32) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_extra_image(image: &ResourceImage) -> Result<()> {
|
fn install_extra_image(image: &ResourceImage) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let filename = format!("citadel-extra-{}.img", image.header().metainfo().version());
|
let filename = format!("citadel-extra-{:03}.img", image.header().metainfo().version());
|
||||||
install_image_file(image, filename.as_str())?;
|
install_image_file(image, filename.as_str())?;
|
||||||
remove_old_extra_images(image)?;
|
remove_old_extra_images(image)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_old_extra_images(image: &ResourceImage) -> Result<()> {
|
fn remove_old_extra_images(image: &ResourceImage) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let new_meta = image.header().metainfo();
|
let new_meta = image.header().metainfo();
|
||||||
let shasum = new_meta.shasum();
|
let shasum = new_meta.shasum();
|
||||||
let target_dir = target_directory(image)?;
|
let target_dir = target_directory(image)?;
|
||||||
util::read_directory(&target_dir, |dent| {
|
Ok(util::read_directory(&target_dir, |dent| {
|
||||||
let path = dent.path();
|
let path = dent.path();
|
||||||
maybe_remove_old_extra_image(&path, shasum)
|
maybe_remove_old_extra_image(&path, shasum).unwrap();
|
||||||
})
|
Ok(())
|
||||||
|
})?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn maybe_remove_old_extra_image(path: &Path, shasum: &str) -> Result<()> {
|
fn maybe_remove_old_extra_image(
|
||||||
|
path: &Path,
|
||||||
|
shasum: &str,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let header = ImageHeader::from_file(&path)?;
|
let header = ImageHeader::from_file(&path)?;
|
||||||
if !header.is_magic_valid() {
|
if !header.is_magic_valid() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -172,21 +180,21 @@ fn maybe_remove_old_extra_image(path: &Path, shasum: &str) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_kernel_image(image: &mut ResourceImage) -> Result<()> {
|
fn install_kernel_image(image: &mut ResourceImage) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
if !Path::new("/boot/loader/loader.conf").exists() {
|
if !Path::new("/boot/loader/loader.conf").exists() {
|
||||||
bail!("failed to automount /boot partition. Please manually mount correct partition.");
|
panic!("failed to automount /boot partition. Please manually mount correct partition.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let metainfo = image.header().metainfo();
|
let metainfo = image.header().metainfo();
|
||||||
let version = metainfo.version();
|
let version = metainfo.version();
|
||||||
let kernel_version = match metainfo.kernel_version() {
|
let kernel_version = match metainfo.kernel_version() {
|
||||||
Some(kv) => kv,
|
Some(kv) => kv,
|
||||||
None => bail!("kernel image does not have kernel version field"),
|
None => panic!("kernel image does not have kernel version field"),
|
||||||
};
|
};
|
||||||
info!("kernel version is {}", kernel_version);
|
info!("kernel version is {}", kernel_version);
|
||||||
install_kernel_file(image, &kernel_version)?;
|
install_kernel_file(image, &kernel_version)?;
|
||||||
|
|
||||||
let filename = format!("citadel-kernel-{}-{}.img", kernel_version, version);
|
let filename = format!("citadel-kernel-{}-{:03}.img", kernel_version, version);
|
||||||
install_image_file(image, &filename)?;
|
install_image_file(image, &filename)?;
|
||||||
|
|
||||||
let all_versions = all_boot_kernel_versions()?;
|
let all_versions = all_boot_kernel_versions()?;
|
||||||
@@ -194,7 +202,7 @@ fn install_kernel_image(image: &mut ResourceImage) -> Result<()> {
|
|||||||
let mut remove_paths = Vec::new();
|
let mut remove_paths = Vec::new();
|
||||||
util::read_directory(&image_dir, |dent| {
|
util::read_directory(&image_dir, |dent| {
|
||||||
let path = dent.path();
|
let path = dent.path();
|
||||||
if is_unused_kernel_image(&path, &all_versions)? {
|
if is_unused_kernel_image(&path, &all_versions).unwrap() {
|
||||||
remove_paths.push(path);
|
remove_paths.push(path);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -206,7 +214,7 @@ fn install_kernel_image(image: &mut ResourceImage) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_unused_kernel_image(path: &Path, versions: &HashSet<String>) -> Result<bool> {
|
fn is_unused_kernel_image(path: &Path, versions: &HashSet<String>) -> Result<bool, Box<dyn std::error::Error>> {
|
||||||
let header = ImageHeader::from_file(path)?;
|
let header = ImageHeader::from_file(path)?;
|
||||||
if !header.is_magic_valid() {
|
if !header.is_magic_valid() {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
@@ -226,23 +234,25 @@ fn is_unused_kernel_image(path: &Path, versions: &HashSet<String>) -> Result<boo
|
|||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_kernel_file(image: &mut ResourceImage, kernel_version: &str) -> Result<()> {
|
fn install_kernel_file(
|
||||||
|
image: &mut ResourceImage,
|
||||||
|
kernel_version: &str,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let mountpoint = Path::new("/run/citadel/images/kernel-install.mountpoint");
|
let mountpoint = Path::new("/run/citadel/images/kernel-install.mountpoint");
|
||||||
info!("Temporarily mounting kernel resource image");
|
info!("Temporarily mounting kernel resource image");
|
||||||
let mut handle = image.mount_at(mountpoint)?;
|
let mut handle = image.mount_at(mountpoint)?;
|
||||||
let kernel_path = mountpoint.join("kernel/bzImage");
|
let kernel_path = mountpoint.join("kernel/bzImage");
|
||||||
if !kernel_path.exists() {
|
if !kernel_path.exists() {
|
||||||
handle.unmount()?;
|
handle.unmount()?;
|
||||||
bail!("kernel not found in kernel resource image at /kernel/bzImage")
|
panic!("kernel not found in kernel resource image at /kernel/bzImage")
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = KernelInstaller::install_kernel(&kernel_path, kernel_version);
|
KernelInstaller::install_kernel(&kernel_path, kernel_version)?;
|
||||||
info!("Unmounting kernel resource image");
|
info!("Unmounting kernel resource image");
|
||||||
handle.unmount()?;
|
Ok(handle.unmount()?)
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn all_boot_kernel_versions() -> Result<HashSet<String>> {
|
fn all_boot_kernel_versions() -> Result<HashSet<String>, Box<dyn std::error::Error>> {
|
||||||
let mut result = HashSet::new();
|
let mut result = HashSet::new();
|
||||||
util::read_directory("/boot", |dent| {
|
util::read_directory("/boot", |dent| {
|
||||||
if is_kernel_dirent(&dent) {
|
if is_kernel_dirent(&dent) {
|
||||||
@@ -264,7 +274,10 @@ fn is_kernel_dirent(dirent: &DirEntry) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_image_file(image: &ResourceImage, filename: &str) -> Result<()> {
|
fn install_image_file(
|
||||||
|
image: &ResourceImage,
|
||||||
|
filename: &str,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let image_dir = target_directory(image)?;
|
let image_dir = target_directory(image)?;
|
||||||
let image_dest = image_dir.join(filename);
|
let image_dest = image_dir.join(filename);
|
||||||
if image_dest.exists() {
|
if image_dest.exists() {
|
||||||
@@ -275,14 +288,14 @@ fn install_image_file(image: &ResourceImage, filename: &str) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn target_directory(image: &ResourceImage) -> Result<PathBuf> {
|
fn target_directory(image: &ResourceImage) -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||||
let metainfo = image.header().metainfo();
|
let metainfo = image.header().metainfo();
|
||||||
let channel = metainfo.channel();
|
let channel = metainfo.channel();
|
||||||
validate_channel_name(channel)?;
|
validate_channel_name(channel)?;
|
||||||
Ok(Path::new("/storage/resources").join(channel))
|
Ok(Path::new("/storage/resources").join(channel))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rotate(path: &Path) -> Result<()> {
|
fn rotate(path: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
if !path.exists() || path.file_name().is_none() {
|
if !path.exists() || path.file_name().is_none() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -293,14 +306,17 @@ fn rotate(path: &Path) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_channel_name(channel: &str) -> Result<()> {
|
fn validate_channel_name(channel: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
if !channel.chars().all(|c| c.is_ascii_lowercase()) {
|
if !channel.chars().all(|c| c.is_ascii_lowercase()) {
|
||||||
bail!("image has invalid channel name '{}'", channel);
|
panic!("image has invalid channel name '{}'", channel);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_rootfs_image(image: &ResourceImage, flags: u32) -> Result<()> {
|
fn install_rootfs_image(
|
||||||
|
image: &ResourceImage,
|
||||||
|
flags: u32,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let quiet = flags & FLAG_QUIET != 0;
|
let quiet = flags & FLAG_QUIET != 0;
|
||||||
let partition = choose_install_partition(!quiet)?;
|
let partition = choose_install_partition(!quiet)?;
|
||||||
|
|
||||||
@@ -315,7 +331,7 @@ fn install_rootfs_image(image: &ResourceImage, flags: u32) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_prefer_boot() -> Result<()> {
|
fn clear_prefer_boot() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
for mut p in Partition::rootfs_partitions()? {
|
for mut p in Partition::rootfs_partitions()? {
|
||||||
if p.is_initialized() && p.header().has_flag(ImageHeader::FLAG_PREFER_BOOT) {
|
if p.is_initialized() && p.header().has_flag(ImageHeader::FLAG_PREFER_BOOT) {
|
||||||
p.clear_flag_and_write(ImageHeader::FLAG_PREFER_BOOT)?;
|
p.clear_flag_and_write(ImageHeader::FLAG_PREFER_BOOT)?;
|
||||||
@@ -332,7 +348,7 @@ fn bool_to_yesno(val: bool) -> &'static str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn choose_install_partition(verbose: bool) -> Result<Partition> {
|
fn choose_install_partition(verbose: bool) -> Result<Partition, Box<dyn std::error::Error>> {
|
||||||
let partitions = Partition::rootfs_partitions()?;
|
let partitions = Partition::rootfs_partitions()?;
|
||||||
|
|
||||||
if verbose {
|
if verbose {
|
||||||
@@ -362,5 +378,5 @@ fn choose_install_partition(verbose: bool) -> Result<Partition> {
|
|||||||
return Ok(p.clone())
|
return Ok(p.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bail!("no suitable install partition found")
|
panic!("no suitable install partition found")
|
||||||
}
|
}
|
||||||
|
|||||||
5
data/com.subgraph.RealmConfig.desktop
Normal file
5
data/com.subgraph.RealmConfig.desktop
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Name=RealmConfig
|
||||||
|
Type=Application
|
||||||
|
Icon=org.gnome.Settings
|
||||||
|
NoDisplay=true
|
||||||
@@ -7,12 +7,10 @@
|
|||||||
<busconfig>
|
<busconfig>
|
||||||
<policy user="root">
|
<policy user="root">
|
||||||
<allow own="com.subgraph.realms"/>
|
<allow own="com.subgraph.realms"/>
|
||||||
<allow own="com.subgraph.Realms2"/>
|
|
||||||
</policy>
|
</policy>
|
||||||
|
|
||||||
<policy context="default">
|
<policy context="default">
|
||||||
<allow send_destination="com.subgraph.realms"/>
|
<allow send_destination="com.subgraph.realms"/>
|
||||||
<allow send_destination="com.subgraph.Realms2"/>
|
|
||||||
<allow send_destination="com.subgraph.realms"
|
<allow send_destination="com.subgraph.realms"
|
||||||
send_interface="org.freedesktop.DBus.Properties"/>
|
send_interface="org.freedesktop.DBus.Properties"/>
|
||||||
<allow send_destination="com.subgraph.realms"
|
<allow send_destination="com.subgraph.realms"
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "launch-gnome-software"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
libcitadel = { path = "../libcitadel" }
|
|
||||||
anyhow = "1.0"
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
use std::env;
|
|
||||||
|
|
||||||
use libcitadel::{Logger, LogLevel, Realm, Realms, util};
|
|
||||||
use libcitadel::flatpak::GnomeSoftwareLauncher;
|
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
|
||||||
|
|
||||||
|
|
||||||
fn realm_arg() -> Option<String> {
|
|
||||||
let mut args = env::args();
|
|
||||||
while let Some(arg) = args.next() {
|
|
||||||
if arg == "--realm" {
|
|
||||||
return args.next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn choose_realm() -> Result<Realm> {
|
|
||||||
let mut realms = Realms::load()?;
|
|
||||||
if let Some(realm_name) = realm_arg() {
|
|
||||||
match realms.by_name(&realm_name) {
|
|
||||||
None => bail!("realm '{}' not found", realm_name),
|
|
||||||
Some(realm) => return Ok(realm),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let realm = match realms.current() {
|
|
||||||
Some(realm) => realm,
|
|
||||||
None => bail!("no current realm"),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(realm)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_arg(arg: &str) -> bool {
|
|
||||||
env::args()
|
|
||||||
.skip(1)
|
|
||||||
.any(|s| s == arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn launch() -> Result<()> {
|
|
||||||
let realm = choose_realm()?;
|
|
||||||
if !util::is_euid_root() {
|
|
||||||
bail!("Must be run with root euid");
|
|
||||||
}
|
|
||||||
let mut launcher = GnomeSoftwareLauncher::new(realm)?;
|
|
||||||
|
|
||||||
if has_arg("--quit") {
|
|
||||||
launcher.quit()?;
|
|
||||||
} else {
|
|
||||||
if has_arg("--shell") {
|
|
||||||
launcher.set_run_shell();
|
|
||||||
}
|
|
||||||
launcher.launch()?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
if has_arg("--verbose") {
|
|
||||||
Logger::set_log_level(LogLevel::Verbose);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(err) = launch() {
|
|
||||||
eprintln!("Error: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,6 @@ nix = "0.17.0"
|
|||||||
toml = "0.5"
|
toml = "0.5"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "=1.0.1"
|
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
sodiumoxide = "0.2"
|
sodiumoxide = "0.2"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
@@ -20,10 +19,6 @@ walkdir = "2"
|
|||||||
dbus = "0.6"
|
dbus = "0.6"
|
||||||
posix-acl = "1.0.0"
|
posix-acl = "1.0.0"
|
||||||
procfs = "0.12.0"
|
procfs = "0.12.0"
|
||||||
anyhow = "1.0"
|
|
||||||
clap = "4.5"
|
|
||||||
tempfile = "3.21"
|
|
||||||
semver = "1.0"
|
|
||||||
|
|
||||||
[dependencies.inotify]
|
[dependencies.inotify]
|
||||||
version = "0.8"
|
version = "0.8"
|
||||||
|
|||||||
@@ -78,9 +78,3 @@ impl Display for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<fmt::Error> for crate::Error {
|
|
||||||
fn from(e: fmt::Error) -> Self {
|
|
||||||
format_err!("Error formatting string: {}", e).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,221 +0,0 @@
|
|||||||
use std::ffi::OsStr;
|
|
||||||
use std::{fs, io};
|
|
||||||
use std::fs::File;
|
|
||||||
use std::os::fd::AsRawFd;
|
|
||||||
use std::os::unix::process::CommandExt;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
use crate::{Logger, LogLevel, Result, verbose};
|
|
||||||
|
|
||||||
const BWRAP_PATH: &str = "/usr/libexec/flatpak-bwrap";
|
|
||||||
|
|
||||||
pub struct BubbleWrap {
|
|
||||||
command: Command,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BubbleWrap {
|
|
||||||
|
|
||||||
pub fn new() -> Self {
|
|
||||||
BubbleWrap {
|
|
||||||
command: Command::new(BWRAP_PATH),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
|
|
||||||
self.command.arg(arg);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> &mut Self {
|
|
||||||
for arg in args {
|
|
||||||
self.add_arg(arg.as_ref());
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ro_bind(&mut self, path_list: &[&str]) -> &mut Self {
|
|
||||||
for &path in path_list {
|
|
||||||
self.add_args(&["--ro-bind", path, path]);
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ro_bind_to(&mut self, src: &str, dest: &str) -> &mut Self {
|
|
||||||
self.add_args(&["--ro-bind", src, dest])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bind_to(&mut self, src: &str, dest: &str) -> &mut Self {
|
|
||||||
self.add_args(&["--bind", src, dest])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_dirs(&mut self, dir_list: &[&str]) -> &mut Self {
|
|
||||||
for &dir in dir_list {
|
|
||||||
self.add_args(&["--dir", dir]);
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_symlinks(&mut self, links: &[(&str, &str)]) -> &mut Self {
|
|
||||||
for (src,dest) in links {
|
|
||||||
self.add_args(&["--symlink", src, dest]);
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mount_dev(&mut self) -> &mut Self {
|
|
||||||
self.add_args(&["--dev", "/dev"])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mount_proc(&mut self) -> &mut Self {
|
|
||||||
self.add_args(&["--proc", "/proc"])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dev_bind(&mut self, path: &str) -> &mut Self {
|
|
||||||
self.add_args(&["--dev-bind", path, path])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear_env(&mut self) -> &mut Self {
|
|
||||||
self.add_arg("--clearenv")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_env_list(&mut self, env_list: &[&str]) -> &mut Self {
|
|
||||||
for line in env_list {
|
|
||||||
if let Some((k,v)) = line.split_once("=") {
|
|
||||||
self.add_args(&["--setenv", k, v]);
|
|
||||||
} else {
|
|
||||||
eprintln!("Warning: environment variable '{}' does not have = delimiter. Ignoring", line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unshare_all(&mut self) -> &mut Self {
|
|
||||||
self.add_arg("--unshare-all")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn share_net(&mut self) -> &mut Self {
|
|
||||||
self.add_arg("--share-net")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn log_command(&self) {
|
|
||||||
let mut buffer = String::new();
|
|
||||||
verbose!("{}", BWRAP_PATH);
|
|
||||||
for arg in self.command.get_args() {
|
|
||||||
if let Some(s) = arg.to_str() {
|
|
||||||
if s.starts_with("-") {
|
|
||||||
if !buffer.is_empty() {
|
|
||||||
verbose!(" {}", buffer);
|
|
||||||
buffer.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !buffer.is_empty() {
|
|
||||||
buffer.push(' ');
|
|
||||||
}
|
|
||||||
buffer.push_str(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !buffer.is_empty() {
|
|
||||||
verbose!(" {}", buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn status_file(&mut self, status_file: &File) -> &mut Self {
|
|
||||||
// Rust sets O_CLOEXEC when opening files so we create
|
|
||||||
// a new descriptor that will remain open across exec()
|
|
||||||
let dup_fd = unsafe {
|
|
||||||
libc::dup(status_file.as_raw_fd())
|
|
||||||
};
|
|
||||||
if dup_fd == -1 {
|
|
||||||
warn!("Failed to dup() status file descriptor: {}", io::Error::last_os_error());
|
|
||||||
warn!("Skipping --json-status-fd argument");
|
|
||||||
self
|
|
||||||
} else {
|
|
||||||
self.add_arg("--json-status-fd")
|
|
||||||
.add_arg(dup_fd.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn launch<S: AsRef<OsStr>>(&mut self, cmd: &[S]) -> Result<()> {
|
|
||||||
if Logger::is_log_level(LogLevel::Verbose) {
|
|
||||||
self.log_command();
|
|
||||||
let s = cmd.iter().map(|s| format!("{} ", s.as_ref().to_str().unwrap())).collect::<String>();
|
|
||||||
verbose!(" {}", s)
|
|
||||||
}
|
|
||||||
self.add_args(cmd);
|
|
||||||
let err = self.command.exec();
|
|
||||||
bail!("failed to exec bubblewrap: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize,Clone)]
|
|
||||||
#[serde(rename_all="kebab-case")]
|
|
||||||
pub struct BubbleWrapRunningStatus {
|
|
||||||
pub child_pid: u64,
|
|
||||||
pub cgroup_namespace: u64,
|
|
||||||
pub ipc_namespace: u64,
|
|
||||||
pub mnt_namespace: u64,
|
|
||||||
pub pid_namespace: u64,
|
|
||||||
pub uts_namespace: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize,Clone)]
|
|
||||||
#[serde(rename_all="kebab-case")]
|
|
||||||
pub struct BubbleWrapExitStatus {
|
|
||||||
pub exit_code: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct BubbleWrapStatus {
|
|
||||||
running: BubbleWrapRunningStatus,
|
|
||||||
exit: Option<BubbleWrapExitStatus>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BubbleWrapStatus {
|
|
||||||
pub fn parse_file(path: impl AsRef<Path>) -> Result<Option<Self>> {
|
|
||||||
if !path.as_ref().exists() {
|
|
||||||
return Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
let s = fs::read_to_string(path)
|
|
||||||
.map_err(context!("error reading status file"))?;
|
|
||||||
|
|
||||||
let mut lines = s.lines();
|
|
||||||
let running = match lines.next() {
|
|
||||||
None => return Ok(None),
|
|
||||||
Some(s) => serde_json::from_str::<BubbleWrapRunningStatus>(s)
|
|
||||||
.map_err(context!("failed to parse status line ({})", s))?
|
|
||||||
};
|
|
||||||
|
|
||||||
let exit = match lines.next() {
|
|
||||||
None => None,
|
|
||||||
Some(s) => Some(serde_json::from_str::<BubbleWrapExitStatus>(s)
|
|
||||||
.map_err(context!("failed to parse exit line ({})", s))?)
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Some(BubbleWrapStatus {
|
|
||||||
running,
|
|
||||||
exit
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_running(&self) -> bool {
|
|
||||||
self.exit.is_none()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn running_status(&self) -> &BubbleWrapRunningStatus {
|
|
||||||
&self.running
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn child_pid(&self) -> u64 {
|
|
||||||
self.running.child_pid
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pid_namespace(&self) -> u64 {
|
|
||||||
self.running.pid_namespace
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn exit_status(&self) -> Option<&BubbleWrapExitStatus> {
|
|
||||||
self.exit.as_ref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,259 +0,0 @@
|
|||||||
use std::{fs, io};
|
|
||||||
use std::fs::File;
|
|
||||||
use std::os::unix::fs::FileTypeExt;
|
|
||||||
use std::os::unix::prelude::CommandExt;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
use crate::{Realm, Result, util};
|
|
||||||
use crate::flatpak::{BubbleWrap, BubbleWrapStatus, SANDBOX_STATUS_FILE_DIRECTORY, SandboxStatus};
|
|
||||||
use crate::flatpak::netns::NetNS;
|
|
||||||
|
|
||||||
|
|
||||||
const FLATPAK_PATH: &str = "/usr/bin/flatpak";
|
|
||||||
|
|
||||||
const ENVIRONMENT: &[&str; 7] = &[
|
|
||||||
"HOME=/home/citadel",
|
|
||||||
"USER=citadel",
|
|
||||||
"XDG_RUNTIME_DIR=/run/user/1000",
|
|
||||||
"XDG_DATA_DIRS=/home/citadel/.local/share/flatpak/exports/share:/usr/share",
|
|
||||||
"TERM=xterm-256color",
|
|
||||||
"GTK_A11Y=none",
|
|
||||||
"FLATPAK_USER_DIR=/home/citadel/realm-flatpak",
|
|
||||||
];
|
|
||||||
|
|
||||||
const FLATHUB_URL: &str = "https://dl.flathub.org/repo/flathub.flatpakrepo";
|
|
||||||
|
|
||||||
pub struct GnomeSoftwareLauncher {
|
|
||||||
realm: Realm,
|
|
||||||
status: Option<BubbleWrapStatus>,
|
|
||||||
netns: NetNS,
|
|
||||||
run_shell: bool,
|
|
||||||
}
|
|
||||||
impl GnomeSoftwareLauncher {
|
|
||||||
|
|
||||||
pub fn new(realm: Realm) -> Result<Self> {
|
|
||||||
let sandbox_status = SandboxStatus::load(SANDBOX_STATUS_FILE_DIRECTORY)?;
|
|
||||||
let status = sandbox_status.realm_status(&realm);
|
|
||||||
let netns = NetNS::new(NetNS::GS_NETNS_NAME);
|
|
||||||
Ok(GnomeSoftwareLauncher {
|
|
||||||
realm,
|
|
||||||
status,
|
|
||||||
netns,
|
|
||||||
run_shell: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_run_shell(&mut self) {
|
|
||||||
self.run_shell = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ensure_flatpak_dir(&self) -> Result<()> {
|
|
||||||
let flatpak_user_dir = self.realm.base_path_file("flatpak");
|
|
||||||
if !flatpak_user_dir.exists() {
|
|
||||||
if let Err(err) = fs::create_dir(&flatpak_user_dir) {
|
|
||||||
bail!("failed to create realm flatpak directory ({}): {}", flatpak_user_dir.display(), err);
|
|
||||||
}
|
|
||||||
util::chown_user(&flatpak_user_dir)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_flathub(&self) -> Result<()> {
|
|
||||||
let flatpak_user_dir = self.realm.base_path_file("flatpak");
|
|
||||||
|
|
||||||
match Command::new(FLATPAK_PATH)
|
|
||||||
.env("FLATPAK_USER_DIR", flatpak_user_dir)
|
|
||||||
.arg("remote-add")
|
|
||||||
.arg("--user")
|
|
||||||
.arg("--if-not-exists")
|
|
||||||
.arg("flathub")
|
|
||||||
.arg(FLATHUB_URL)
|
|
||||||
.status() {
|
|
||||||
Ok(status) => {
|
|
||||||
if status.success() {
|
|
||||||
Ok(())
|
|
||||||
|
|
||||||
} else {
|
|
||||||
bail!("failed to add flathub repo")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(err) => bail!("error running flatpak command: {}", err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scan_tmp_directory(path: &Path) -> io::Result<Option<String>> {
|
|
||||||
for entry in fs::read_dir(&path)? {
|
|
||||||
let entry = entry?;
|
|
||||||
if entry.file_type()?.is_socket() {
|
|
||||||
if let Some(filename) = entry.path().file_name() {
|
|
||||||
if let Some(filename) = filename.to_str() {
|
|
||||||
if filename.starts_with("dbus-") {
|
|
||||||
return Ok(Some(format!("/tmp/{}", filename)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_dbus_socket(&self) -> Result<String> {
|
|
||||||
let pid = self.running_pid()?;
|
|
||||||
let tmp_dir = PathBuf::from(format!("/proc/{}/root/tmp", pid));
|
|
||||||
if !tmp_dir.is_dir() {
|
|
||||||
bail!("no /tmp directory found for process pid={}", pid);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(s) = Self::scan_tmp_directory(&tmp_dir)
|
|
||||||
.map_err(context!("error reading directory {}", tmp_dir.display()))? {
|
|
||||||
Ok(s)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
bail!("no dbus socket found in /tmp directory for process pid={}", pid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn launch_sandbox(&self, status_file: &File) -> Result<()> {
|
|
||||||
self.ensure_flatpak_dir()?;
|
|
||||||
if let Err(err) = self.netns.nsenter() {
|
|
||||||
bail!("Failed to enter 'gnome-software' network namespace: {}", err);
|
|
||||||
}
|
|
||||||
verbose!("Entered network namespace ({})", NetNS::GS_NETNS_NAME);
|
|
||||||
|
|
||||||
if let Err(err) = util::drop_privileges(1000, 1000) {
|
|
||||||
bail!("Failed to drop privileges to uid = gid = 1000: {}", err);
|
|
||||||
}
|
|
||||||
verbose!("Dropped privileges (uid=1000, gid=1000)");
|
|
||||||
|
|
||||||
self.add_flathub()?;
|
|
||||||
let flatpak_user_dir = self.realm.base_path_file("flatpak");
|
|
||||||
let flatpak_user_dir = flatpak_user_dir.to_str().unwrap();
|
|
||||||
|
|
||||||
let cmd = if self.run_shell { "/usr/bin/bash" } else { "/usr/bin/gnome-software"};
|
|
||||||
verbose!("Running command in sandbox: {}", cmd);
|
|
||||||
|
|
||||||
BubbleWrap::new()
|
|
||||||
.ro_bind(&[
|
|
||||||
"/usr/bin",
|
|
||||||
"/usr/lib",
|
|
||||||
"/usr/libexec",
|
|
||||||
"/usr/share/dbus-1",
|
|
||||||
"/usr/share/icons",
|
|
||||||
"/usr/share/mime",
|
|
||||||
"/usr/share/X11",
|
|
||||||
"/usr/share/glib-2.0",
|
|
||||||
"/usr/share/xml",
|
|
||||||
"/usr/share/drirc.d",
|
|
||||||
"/usr/share/fontconfig",
|
|
||||||
"/usr/share/fonts",
|
|
||||||
"/usr/share/zoneinfo",
|
|
||||||
"/usr/share/swcatalog",
|
|
||||||
|
|
||||||
"/etc/passwd",
|
|
||||||
"/etc/machine-id",
|
|
||||||
"/etc/nsswitch.conf",
|
|
||||||
"/etc/fonts",
|
|
||||||
"/etc/ssl",
|
|
||||||
"/sys/dev/char", "/sys/devices",
|
|
||||||
"/run/user/1000/wayland-0",
|
|
||||||
])
|
|
||||||
.ro_bind_to("/run/NetworkManager/resolv.conf", "/etc/resolv.conf")
|
|
||||||
.bind_to(flatpak_user_dir, "/home/citadel/realm-flatpak")
|
|
||||||
.create_symlinks(&[
|
|
||||||
("usr/lib", "/lib64"),
|
|
||||||
("usr/bin", "/bin"),
|
|
||||||
("/tmp", "/var/tmp"),
|
|
||||||
])
|
|
||||||
.create_dirs(&[
|
|
||||||
"/var/lib/flatpak",
|
|
||||||
"/home/citadel",
|
|
||||||
"/tmp",
|
|
||||||
"/sys/block", "/sys/bus", "/sys/class",
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
.mount_dev()
|
|
||||||
.dev_bind("/dev/dri")
|
|
||||||
.mount_proc()
|
|
||||||
.unshare_all()
|
|
||||||
.share_net()
|
|
||||||
|
|
||||||
.clear_env()
|
|
||||||
.set_env_list(ENVIRONMENT)
|
|
||||||
|
|
||||||
.status_file(status_file)
|
|
||||||
|
|
||||||
.launch(&["dbus-run-session", "--", cmd])?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_realm_status_file(&self) -> Result<File> {
|
|
||||||
let path = Path::new(SANDBOX_STATUS_FILE_DIRECTORY).join(self.realm.name());
|
|
||||||
File::create(&path)
|
|
||||||
.map_err(context!("failed to open sandbox status file {}", path.display()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn launch(&self) -> Result<()> {
|
|
||||||
self.netns.ensure_exists()?;
|
|
||||||
if self.is_running() {
|
|
||||||
let cmd = if self.run_shell { "/usr/bin/bash" } else { "/usr/bin/gnome-software"};
|
|
||||||
self.launch_in_running_sandbox(&[cmd])?;
|
|
||||||
} else {
|
|
||||||
let status_file = self.new_realm_status_file()?;
|
|
||||||
self.ensure_flatpak_dir()?;
|
|
||||||
self.launch_sandbox(&status_file)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn quit(&self) -> Result<()> {
|
|
||||||
if self.is_running() {
|
|
||||||
self.launch_in_running_sandbox(&["/usr/bin/gnome-software", "--quit"])?;
|
|
||||||
} else {
|
|
||||||
warn!("No running sandbox found for realm {}", self.realm.name());
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_running(&self) -> bool {
|
|
||||||
self.status.as_ref()
|
|
||||||
.map(|s| s.is_running())
|
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn running_pid(&self) -> Result<u64> {
|
|
||||||
self.status.as_ref()
|
|
||||||
.map(|s| s.child_pid())
|
|
||||||
.ok_or(format_err!("no sandbox status available for realm '{}',", self.realm.name()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dbus_session_address(&self) -> Result<String> {
|
|
||||||
let dbus_socket = Self::find_dbus_socket(&self)?;
|
|
||||||
Ok(format!("unix:path={}", dbus_socket))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn launch_in_running_sandbox(&self, command: &[&str]) -> Result<()> {
|
|
||||||
let dbus_address = self.dbus_session_address()?;
|
|
||||||
|
|
||||||
let pid = self.running_pid()?.to_string();
|
|
||||||
let mut env = ENVIRONMENT.iter()
|
|
||||||
.map(|s| s.split_once('=').unwrap())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
env.push(("DBUS_SESSION_BUS_ADDRESS", dbus_address.as_str()));
|
|
||||||
|
|
||||||
let err = Command::new("/usr/bin/nsenter")
|
|
||||||
.env_clear()
|
|
||||||
.envs( env )
|
|
||||||
.args(&[
|
|
||||||
"--all",
|
|
||||||
"--target", pid.as_str(),
|
|
||||||
"--setuid", "1000",
|
|
||||||
"--setgid", "1000",
|
|
||||||
])
|
|
||||||
.args(command)
|
|
||||||
.exec();
|
|
||||||
|
|
||||||
Err(format_err!("failed to execute nsenter: {}", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
|
|
||||||
pub(crate) mod setup;
|
|
||||||
pub(crate) mod status;
|
|
||||||
|
|
||||||
pub(crate) mod bubblewrap;
|
|
||||||
|
|
||||||
pub(crate) mod launcher;
|
|
||||||
|
|
||||||
pub(crate) mod netns;
|
|
||||||
|
|
||||||
pub use status::SandboxStatus;
|
|
||||||
|
|
||||||
pub use bubblewrap::{BubbleWrap,BubbleWrapStatus,BubbleWrapRunningStatus,BubbleWrapExitStatus};
|
|
||||||
pub use launcher::GnomeSoftwareLauncher;
|
|
||||||
|
|
||||||
pub const SANDBOX_STATUS_FILE_DIRECTORY: &str = "/run/citadel/realms/gs-sandbox-status";
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
use std::ffi::OsStr;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
use crate::{util,Result};
|
|
||||||
|
|
||||||
|
|
||||||
const BRIDGE_NAME: &str = "vz-clear";
|
|
||||||
const VETH0: &str = "gs-veth0";
|
|
||||||
const VETH1: &str = "gs-veth1";
|
|
||||||
const IP_ADDRESS: &str = "172.17.0.222/24";
|
|
||||||
const GW_ADDRESS: &str = "172.17.0.1";
|
|
||||||
|
|
||||||
|
|
||||||
pub struct NetNS {
|
|
||||||
name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NetNS {
|
|
||||||
pub const GS_NETNS_NAME: &'static str = "gnome-software";
|
|
||||||
pub fn new(name: &str) -> Self {
|
|
||||||
NetNS {
|
|
||||||
name: name.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create(&self) -> crate::Result<()> {
|
|
||||||
Ip::new().link_add_veth(VETH0, VETH1).run()?;
|
|
||||||
Ip::new().link_set_netns(VETH0, &self.name).run()?;
|
|
||||||
Ip::new().link_set_master(VETH1, BRIDGE_NAME).run()?;
|
|
||||||
Ip::new().link_set_dev_up(VETH1).run()?;
|
|
||||||
|
|
||||||
Ip::new().ip_netns_exec_ip(&self.name).addr_add(IP_ADDRESS, VETH0).run()?;
|
|
||||||
Ip::new().ip_netns_exec_ip(&self.name).link_set_dev_up(VETH0).run()?;
|
|
||||||
Ip::new().ip_netns_exec_ip(&self.name).route_add_default(GW_ADDRESS).run()?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
pub fn ensure_exists(&self) -> Result<()> {
|
|
||||||
if Path::new(&format!("/run/netns/{}", self.name)).exists() {
|
|
||||||
verbose!("Network namespace ({}) exists", self.name);
|
|
||||||
return Ok(())
|
|
||||||
}
|
|
||||||
verbose!("Setting up network namespace ({})", self.name);
|
|
||||||
|
|
||||||
Ip::new().netns_add(&self.name).run()
|
|
||||||
.map_err(context!("Failed to add network namespace '{}'", self.name))?;
|
|
||||||
|
|
||||||
if let Err(err) = self.create() {
|
|
||||||
Ip::new().netns_delete(&self.name).run()?;
|
|
||||||
Err(err)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn nsenter(&self) -> Result<()> {
|
|
||||||
util::nsenter_netns(&self.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const IP_PATH: &str = "/usr/sbin/ip";
|
|
||||||
struct Ip {
|
|
||||||
command: Command,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ip {
|
|
||||||
|
|
||||||
fn new() -> Self {
|
|
||||||
let mut command = Command::new(IP_PATH);
|
|
||||||
command.env_clear();
|
|
||||||
Ip { command }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> &mut Self {
|
|
||||||
for arg in args {
|
|
||||||
self.command.arg(arg);
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn netns_add(&mut self, name: &str) -> &mut Self {
|
|
||||||
self.add_args(&["netns", "add", name])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn netns_delete(&mut self, name: &str) -> &mut Self {
|
|
||||||
self.add_args(&["netns", "delete", name])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn link_add_veth(&mut self, name: &str, peer_name: &str) -> &mut Self {
|
|
||||||
self.add_args(&["link", "add", name, "type", "veth", "peer", "name", peer_name])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn link_set_netns(&mut self, iface: &str, netns_name: &str) -> &mut Self {
|
|
||||||
self.add_args(&["link", "set", iface, "netns", netns_name])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn link_set_master(&mut self, iface: &str, bridge_name: &str) -> &mut Self {
|
|
||||||
self.add_args(&["link", "set", iface, "master", bridge_name])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn link_set_dev_up(&mut self, iface: &str) -> &mut Self {
|
|
||||||
self.add_args(&["link", "set", "dev", iface, "up"])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ip_netns_exec_ip(&mut self, netns_name: &str) -> &mut Self {
|
|
||||||
self.add_args(&["netns", "exec", netns_name, IP_PATH])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn addr_add(&mut self, ip_address: &str, dev: &str) -> &mut Self {
|
|
||||||
self.add_args(&["addr", "add", ip_address, "dev", dev])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn route_add_default(&mut self, gateway: &str) -> &mut Self {
|
|
||||||
self.add_args(&["route", "add", "default", "via", gateway])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(&mut self) -> crate::Result<()> {
|
|
||||||
verbose!("{:?}", self.command);
|
|
||||||
match self.command.status() {
|
|
||||||
Ok(status) => {
|
|
||||||
if status.success() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
bail!("IP command ({:?}) did not succeeed.", self.command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
bail!("error running ip command ({:?}): {}", self.command, err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
use std::path::Path;
|
|
||||||
use crate::{Realm, Result, util};
|
|
||||||
|
|
||||||
const GNOME_SOFTWARE_DESKTOP_TEMPLATE: &str = "\
|
|
||||||
[Desktop Entry]
|
|
||||||
Name=Software
|
|
||||||
Comment=Add, remove or update software on this computer
|
|
||||||
Icon=org.gnome.Software
|
|
||||||
Exec=/usr/libexec/launch-gnome-software --realm $REALM_NAME
|
|
||||||
Terminal=false
|
|
||||||
Type=Application
|
|
||||||
Categories=GNOME;GTK;System;PackageManager;
|
|
||||||
Keywords=Updates;Upgrade;Sources;Repositories;Preferences;Install;Uninstall;Program;Software;App;Store;
|
|
||||||
StartupNotify=true
|
|
||||||
";
|
|
||||||
|
|
||||||
const APPLICATION_DIRECTORY: &str = "/home/citadel/.local/share/applications";
|
|
||||||
|
|
||||||
pub struct FlatpakSetup<'a> {
|
|
||||||
realm: &'a Realm,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl <'a> FlatpakSetup<'a> {
|
|
||||||
|
|
||||||
pub fn new(realm: &'a Realm) -> Self {
|
|
||||||
Self { realm }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setup(&self) -> Result<()> {
|
|
||||||
self.write_desktop_file()?;
|
|
||||||
self.ensure_flatpak_directory()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn write_desktop_file(&self) -> Result<()> {
|
|
||||||
let appdir = Path::new(APPLICATION_DIRECTORY);
|
|
||||||
if !appdir.exists() {
|
|
||||||
util::create_dir(appdir)?;
|
|
||||||
if let Some(parent) = appdir.parent().and_then(|p| p.parent()) {
|
|
||||||
util::chown_tree(parent, (1000,1000), true)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let path = appdir.join(format!("realm-{}.org.gnome.Software.desktop", self.realm.name()));
|
|
||||||
util::write_file(path, GNOME_SOFTWARE_DESKTOP_TEMPLATE.replace("$REALM_NAME", self.realm.name()))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ensure_flatpak_directory(&self) -> Result<()> {
|
|
||||||
let path = self.realm.base_path_file("flatpak");
|
|
||||||
if !path.exists() {
|
|
||||||
util::create_dir(&path)?;
|
|
||||||
util::chown_user(&path)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::time::SystemTime;
|
|
||||||
use crate::flatpak::bubblewrap::BubbleWrapStatus;
|
|
||||||
use crate::{Realm, Result, util};
|
|
||||||
|
|
||||||
|
|
||||||
/// Utility function to read modified time from a path.
|
|
||||||
fn modified_time(path: &Path) -> Result<SystemTime> {
|
|
||||||
path.metadata().and_then(|meta| meta.modified())
|
|
||||||
.map_err(context!("failed to read modified time from '{}'", path.display()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Utility function to detect if current modified time of a path
|
|
||||||
/// matches an earlier recorded modified time.
|
|
||||||
fn modified_changed(path: &Path, old_modified: SystemTime) -> bool {
|
|
||||||
if !path.exists() {
|
|
||||||
// Path existed at some earlier point, so something changed
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
match modified_time(path) {
|
|
||||||
Ok(modified) => old_modified != modified,
|
|
||||||
Err(err) => {
|
|
||||||
// Print a warning but assume change
|
|
||||||
warn!("{}", err);
|
|
||||||
true
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Records the content of single entry in a sandbox status directory.
|
|
||||||
///
|
|
||||||
/// The path to the status file as well as the last modified time are
|
|
||||||
/// recorded so that changes in status of a sandbox can be detected.
|
|
||||||
struct StatusEntry {
|
|
||||||
status: BubbleWrapStatus,
|
|
||||||
path: PathBuf,
|
|
||||||
modified: SystemTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl StatusEntry {
|
|
||||||
|
|
||||||
fn load_timestamp_and_status(path: &Path) -> Result<Option<(SystemTime, BubbleWrapStatus)>> {
|
|
||||||
if path.exists() {
|
|
||||||
let modified = modified_time(path)?;
|
|
||||||
if let Some(status) = BubbleWrapStatus::parse_file(path)? {
|
|
||||||
return Ok(Some((modified, status)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load(base_dir: &Path, name: &str) -> Result<Option<Self>> {
|
|
||||||
let path = base_dir.join(name);
|
|
||||||
let result = StatusEntry::load_timestamp_and_status(&path)?
|
|
||||||
.map(|(modified, status)| StatusEntry { status, path, modified });
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_modified(&self) -> bool {
|
|
||||||
modified_changed(&self.path, self.modified)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Holds information about entries in a sandbox status directory.
|
|
||||||
///
|
|
||||||
/// Bubblewrap accepts a command line argument that asks for status
|
|
||||||
/// information to be written as a json structure to a file descriptor.
|
|
||||||
///
|
|
||||||
pub struct SandboxStatus {
|
|
||||||
base_dir: PathBuf,
|
|
||||||
base_modified: SystemTime,
|
|
||||||
entries: HashMap<String, StatusEntry>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SandboxStatus {
|
|
||||||
|
|
||||||
pub fn need_reload(&self) -> bool {
|
|
||||||
if modified_changed(&self.base_dir, self.base_modified) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
self.entries.values().any(|entry| entry.is_modified())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_dir_entry(&mut self, dir_entry: PathBuf) -> Result<()> {
|
|
||||||
fn realm_name_for_path(path: &Path) -> Option<&str> {
|
|
||||||
path.file_name()
|
|
||||||
.and_then(|name| name.to_str())
|
|
||||||
.filter(|name| Realm::is_valid_name(name))
|
|
||||||
|
|
||||||
}
|
|
||||||
if dir_entry.is_file() {
|
|
||||||
if let Some(name) = realm_name_for_path(&dir_entry) {
|
|
||||||
if let Some(entry) = StatusEntry::load(&self.base_dir, name)? {
|
|
||||||
self.entries.insert(name.to_string(), entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reload(&mut self) -> Result<()> {
|
|
||||||
self.entries.clear();
|
|
||||||
self.base_modified = modified_time(&self.base_dir)?;
|
|
||||||
let base_dir = self.base_dir.clone();
|
|
||||||
util::read_directory(&base_dir, |entry| {
|
|
||||||
self.process_dir_entry(entry.path())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new(base_dir: &Path) -> Result<Self> {
|
|
||||||
let base_dir = base_dir.to_owned();
|
|
||||||
let base_modified = modified_time(&base_dir)?;
|
|
||||||
Ok(SandboxStatus {
|
|
||||||
base_dir,
|
|
||||||
base_modified,
|
|
||||||
entries: HashMap::new(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load(directory: impl AsRef<Path>) -> Result<SandboxStatus> {
|
|
||||||
let base_dir = directory.as_ref();
|
|
||||||
if !base_dir.exists() {
|
|
||||||
util::create_dir(base_dir)?;
|
|
||||||
}
|
|
||||||
let mut status = SandboxStatus::new(base_dir)?;
|
|
||||||
status.reload()?;
|
|
||||||
Ok(status)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn realm_status(&self, realm: &Realm) -> Option<BubbleWrapStatus> {
|
|
||||||
self.entries.get(realm.name()).map(|entry| entry.status.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_realm_status_file(&self, realm: &Realm) -> Result<File> {
|
|
||||||
let path = self.base_dir.join(realm.name());
|
|
||||||
File::create(&path)
|
|
||||||
.map_err(context!("failed to open sandbox status file {}", path.display()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -382,7 +382,7 @@ impl ImageHeader {
|
|||||||
self.set_signature(&zeros);
|
self.set_signature(&zeros);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn public_key(&self) -> Result<PublicKey> {
|
pub fn public_key(&self) -> Result<Option<PublicKey>> {
|
||||||
public_key_for_channel(self.metainfo().channel())
|
public_key_for_channel(self.metainfo().channel())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,7 +453,7 @@ pub struct MetaInfo {
|
|||||||
realmfs_owner: Option<String>,
|
realmfs_owner: Option<String>,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
version: String,
|
version: u32,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
timestamp: String,
|
timestamp: String,
|
||||||
@@ -508,8 +508,8 @@ impl MetaInfo {
|
|||||||
Self::str_ref(&self.realmfs_owner)
|
Self::str_ref(&self.realmfs_owner)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn version(&self) -> &str {
|
pub fn version(&self) -> u32 {
|
||||||
&self.version
|
self.version
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn timestamp(&self) -> &str {
|
pub fn timestamp(&self) -> &str {
|
||||||
|
|||||||
@@ -37,17 +37,7 @@ impl PublicKey {
|
|||||||
pub fn verify(&self, data: &[u8], signature: &[u8]) -> bool {
|
pub fn verify(&self, data: &[u8], signature: &[u8]) -> bool {
|
||||||
let sig = sign::Signature::try_from(signature)
|
let sig = sign::Signature::try_from(signature)
|
||||||
.expect("Signature::from_slice() failed");
|
.expect("Signature::from_slice() failed");
|
||||||
let is_valid = sign::verify_detached(&sig, data, &self.0);
|
sign::verify_detached(&sig, data, &self.0)
|
||||||
|
|
||||||
if !is_valid {
|
|
||||||
warn!("Header signature verification FAILED!");
|
|
||||||
warn!(" Public Key: {}", self.to_hex());
|
|
||||||
warn!(" Data (header): {}", hex::encode(data));
|
|
||||||
warn!(" Signature: {}", hex::encode(signature));
|
|
||||||
} else {
|
|
||||||
info!("Header signature verification SUCCESS.");
|
|
||||||
}
|
|
||||||
is_valid
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,6 @@
|
|||||||
#[macro_use] extern crate serde_derive;
|
#[macro_use] extern crate serde_derive;
|
||||||
#[macro_use] extern crate lazy_static;
|
#[macro_use] extern crate lazy_static;
|
||||||
|
|
||||||
use std::fs;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
#[macro_use] pub mod error;
|
#[macro_use] pub mod error;
|
||||||
#[macro_use] mod log;
|
#[macro_use] mod log;
|
||||||
#[macro_use] mod exec;
|
#[macro_use] mod exec;
|
||||||
@@ -23,9 +20,6 @@ pub mod symlink;
|
|||||||
mod realm;
|
mod realm;
|
||||||
pub mod terminal;
|
pub mod terminal;
|
||||||
mod system;
|
mod system;
|
||||||
pub mod updates;
|
|
||||||
|
|
||||||
pub mod flatpak;
|
|
||||||
|
|
||||||
pub use crate::config::OsRelease;
|
pub use crate::config::OsRelease;
|
||||||
pub use crate::blockdev::BlockDev;
|
pub use crate::blockdev::BlockDev;
|
||||||
@@ -38,12 +32,10 @@ pub use crate::realmfs::{RealmFS,Mountpoint};
|
|||||||
pub use crate::keyring::{KeyRing,KernelKey};
|
pub use crate::keyring::{KeyRing,KernelKey};
|
||||||
pub use crate::exec::{Exec,FileRange};
|
pub use crate::exec::{Exec,FileRange};
|
||||||
pub use crate::realmfs::resizer::ResizeSize;
|
pub use crate::realmfs::resizer::ResizeSize;
|
||||||
pub use crate::realmfs::update::RealmFSUpdate;
|
|
||||||
pub use crate::realm::overlay::RealmOverlay;
|
pub use crate::realm::overlay::RealmOverlay;
|
||||||
pub use crate::realm::realm::Realm;
|
pub use crate::realm::realm::Realm;
|
||||||
pub use crate::realm::pidmapper::PidLookupResult;
|
pub use crate::realm::pidmapper::PidLookupResult;
|
||||||
pub use crate::realm::config::{RealmConfig,OverlayType,GLOBAL_CONFIG};
|
pub use crate::realm::config::{RealmConfig,OverlayType,GLOBAL_CONFIG};
|
||||||
pub use crate::realm::liveconfig::LiveConfig;
|
|
||||||
pub use crate::realm::events::RealmEvent;
|
pub use crate::realm::events::RealmEvent;
|
||||||
pub use crate::realm::realms::Realms;
|
pub use crate::realm::realms::Realms;
|
||||||
pub use crate::realm::manager::RealmManager;
|
pub use crate::realm::manager::RealmManager;
|
||||||
@@ -58,36 +50,28 @@ pub fn devkeys() -> KeyPair {
|
|||||||
.expect("Error parsing built in dev channel keys")
|
.expect("Error parsing built in dev channel keys")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn keypair_for_channel_signing(private_key_path: &Path) -> KeyPair {
|
pub fn public_key_for_channel(channel: &str) -> Result<Option<PublicKey>> {
|
||||||
let hex_key = fs::read_to_string(private_key_path)
|
if channel == "dev" {
|
||||||
.expect(&format!("Error reading secret key from {}", private_key_path.display()));
|
return Ok(Some(devkeys().public_key()));
|
||||||
KeyPair::from_hex(hex_key.trim())
|
}
|
||||||
.expect(&format!("Error parsing secret key from {}", private_key_path.display()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn public_key_for_channel(channel: &str) -> Result<PublicKey> {
|
// Look in /etc/os-release
|
||||||
// Kernel command line override for developers
|
if Some(channel) == OsRelease::citadel_channel() {
|
||||||
|
if let Some(hex) = OsRelease::citadel_image_pubkey() {
|
||||||
|
let pubkey = PublicKey::from_hex(hex)?;
|
||||||
|
return Ok(Some(pubkey));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does kernel command line have citadel.channel=name:[hex encoded pubkey]
|
||||||
if Some(channel) == CommandLine::channel_name() {
|
if Some(channel) == CommandLine::channel_name() {
|
||||||
if let Some(hex) = CommandLine::channel_pubkey() {
|
if let Some(hex) = CommandLine::channel_pubkey() {
|
||||||
let pubkey = PublicKey::from_hex(hex)?;
|
let pubkey = PublicKey::from_hex(hex)?;
|
||||||
return Ok(pubkey);
|
return Ok(Some(pubkey))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let key_path = Path::new("/usr/share/citadel/keys/").join(format!("{}.pub", channel));
|
Ok(None)
|
||||||
|
|
||||||
if !key_path.exists() {
|
|
||||||
if channel == "dev" {
|
|
||||||
return Ok(devkeys().public_key());
|
|
||||||
}
|
|
||||||
bail!("Public key not found for channel '{}' at {}", channel, key_path.display());
|
|
||||||
}
|
|
||||||
|
|
||||||
let hex_key = fs::read_to_string(&key_path)
|
|
||||||
.map_err(context!("could not read public key from {}", key_path.display()))?;
|
|
||||||
|
|
||||||
let pubkey = PublicKey::from_hex(hex_key.trim())?;
|
|
||||||
Ok(pubkey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use error::{Result,Error};
|
pub use error::{Result,Error};
|
||||||
|
|||||||
@@ -62,11 +62,6 @@ impl Logger {
|
|||||||
logger.level = level;
|
logger.level = level;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_log_level(level: LogLevel) -> bool {
|
|
||||||
let logger = LOGGER.lock().unwrap();
|
|
||||||
logger.level >= level
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_log_output(output: Box<dyn LogOutput>) {
|
pub fn set_log_output(output: Box<dyn LogOutput>) {
|
||||||
let mut logger = LOGGER.lock().unwrap();
|
let mut logger = LOGGER.lock().unwrap();
|
||||||
logger.output = output;
|
logger.output = output;
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ pub struct Partition {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct HeaderInfo {
|
struct HeaderInfo {
|
||||||
header: Arc<ImageHeader>,
|
header: Arc<ImageHeader>,
|
||||||
pubkey: PublicKey,
|
// None if no public key available for channel named in metainfo
|
||||||
|
pubkey: Option<PublicKey>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Partition {
|
impl Partition {
|
||||||
@@ -42,7 +43,13 @@ impl Partition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let metainfo = header.metainfo();
|
let metainfo = header.metainfo();
|
||||||
let pubkey = public_key_for_channel(metainfo.channel())?;
|
let pubkey = match public_key_for_channel(metainfo.channel()) {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(err) => {
|
||||||
|
warn!("Error parsing pubkey for channel '{}': {}", metainfo.channel(), err);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let header = Arc::new(header);
|
let header = Arc::new(header);
|
||||||
Ok(Some(HeaderInfo {
|
Ok(Some(HeaderInfo {
|
||||||
@@ -97,15 +104,21 @@ impl Partition {
|
|||||||
|
|
||||||
pub fn is_signature_valid(&self) -> bool {
|
pub fn is_signature_valid(&self) -> bool {
|
||||||
if let Some(ref hinfo) = self.hinfo {
|
if let Some(ref hinfo) = self.hinfo {
|
||||||
return hinfo.pubkey.verify(
|
if let Some(ref pubkey) = hinfo.pubkey {
|
||||||
|
return pubkey.verify(
|
||||||
&self.header().metainfo_bytes(),
|
&self.header().metainfo_bytes(),
|
||||||
&self.header().signature())
|
&self.header().signature())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_public_key(&self) -> bool {
|
pub fn has_public_key(&self) -> bool {
|
||||||
self.hinfo.is_some()
|
if let Some(ref h) = self.hinfo {
|
||||||
|
h.pubkey.is_some()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_status(&mut self, status: u8) -> Result<()> {
|
pub fn write_status(&mut self, status: u8) -> Result<()> {
|
||||||
|
|||||||
@@ -77,9 +77,6 @@ pub struct RealmConfig {
|
|||||||
#[serde(rename="use-fuse")]
|
#[serde(rename="use-fuse")]
|
||||||
pub use_fuse: Option<bool>,
|
pub use_fuse: Option<bool>,
|
||||||
|
|
||||||
#[serde(rename="use-flatpak")]
|
|
||||||
pub use_flatpak: Option<bool>,
|
|
||||||
|
|
||||||
#[serde(rename="use-gpu")]
|
#[serde(rename="use-gpu")]
|
||||||
pub use_gpu: Option<bool>,
|
pub use_gpu: Option<bool>,
|
||||||
|
|
||||||
@@ -204,7 +201,6 @@ impl RealmConfig {
|
|||||||
wayland_socket: Some("wayland-0".to_string()),
|
wayland_socket: Some("wayland-0".to_string()),
|
||||||
use_kvm: Some(false),
|
use_kvm: Some(false),
|
||||||
use_fuse: Some(false),
|
use_fuse: Some(false),
|
||||||
use_flatpak: Some(false),
|
|
||||||
use_gpu: Some(false),
|
use_gpu: Some(false),
|
||||||
use_gpu_card0: Some(false),
|
use_gpu_card0: Some(false),
|
||||||
use_network: Some(true),
|
use_network: Some(true),
|
||||||
@@ -237,7 +233,6 @@ impl RealmConfig {
|
|||||||
wayland_socket: None,
|
wayland_socket: None,
|
||||||
use_kvm: None,
|
use_kvm: None,
|
||||||
use_fuse: None,
|
use_fuse: None,
|
||||||
use_flatpak: None,
|
|
||||||
use_gpu: None,
|
use_gpu: None,
|
||||||
use_gpu_card0: None,
|
use_gpu_card0: None,
|
||||||
use_network: None,
|
use_network: None,
|
||||||
@@ -266,44 +261,12 @@ impl RealmConfig {
|
|||||||
self.bool_value(|c| c.use_kvm)
|
self.bool_value(|c| c.use_kvm)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_kvm(&mut self, value: bool) -> bool {
|
|
||||||
if self.kvm() == value {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
self.use_kvm = Some(value);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If `true` device /dev/fuse will be added to realm
|
/// If `true` device /dev/fuse will be added to realm
|
||||||
///
|
///
|
||||||
pub fn fuse(&self) -> bool {
|
pub fn fuse(&self) -> bool {
|
||||||
self.bool_value(|c| c.use_fuse)
|
self.bool_value(|c| c.use_fuse)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_fuse(&mut self, value: bool) -> bool {
|
|
||||||
if self.fuse() == value {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
self.use_fuse = Some(value);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If `true` flatpak directory will be mounted into realm
|
|
||||||
/// and a desktop file will be created to launch gnome-software
|
|
||||||
///
|
|
||||||
pub fn flatpak(&self) -> bool {
|
|
||||||
self.bool_value(|c| c.use_flatpak)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_flatpak(&mut self, value: bool) -> bool {
|
|
||||||
if self.flatpak() == value {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
self.use_flatpak = Some(value);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// If `true` render node device /dev/dri/renderD128 will be added to realm.
|
/// If `true` render node device /dev/dri/renderD128 will be added to realm.
|
||||||
///
|
///
|
||||||
/// This enables hardware graphics acceleration in realm.
|
/// This enables hardware graphics acceleration in realm.
|
||||||
@@ -311,28 +274,12 @@ impl RealmConfig {
|
|||||||
self.bool_value(|c| c.use_gpu)
|
self.bool_value(|c| c.use_gpu)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_gpu(&mut self, value: bool) -> bool {
|
|
||||||
if self.gpu() == value {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
self.use_gpu = Some(value);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If `true` and `self.gpu()` is also true, privileged device /dev/dri/card0 will be
|
/// If `true` and `self.gpu()` is also true, privileged device /dev/dri/card0 will be
|
||||||
/// added to realm.
|
/// added to realm.
|
||||||
pub fn gpu_card0(&self) -> bool {
|
pub fn gpu_card0(&self) -> bool {
|
||||||
self.bool_value(|c| c.use_gpu_card0)
|
self.bool_value(|c| c.use_gpu_card0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_gpu_card0(&mut self, value: bool) -> bool {
|
|
||||||
if self.gpu_card0() == value {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
self.use_gpu_card0 = Some(value);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If `true` the /Shared directory will be mounted in home directory of realm.
|
/// If `true` the /Shared directory will be mounted in home directory of realm.
|
||||||
///
|
///
|
||||||
/// This directory is shared between all running realms and is an easy way to move files
|
/// This directory is shared between all running realms and is an easy way to move files
|
||||||
@@ -341,28 +288,12 @@ impl RealmConfig {
|
|||||||
self.bool_value(|c| c.use_shared_dir)
|
self.bool_value(|c| c.use_shared_dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_shared_dir(&mut self, value: bool) -> bool {
|
|
||||||
if self.shared_dir() == value {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
self.use_shared_dir = Some(value);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If `true` the mount directory for external storage devices will be bind mounted as /Media
|
/// If `true` the mount directory for external storage devices will be bind mounted as /Media
|
||||||
///
|
///
|
||||||
pub fn media_dir(&self) -> bool {
|
pub fn media_dir(&self) -> bool {
|
||||||
self.bool_value(|c| c.use_media_dir)
|
self.bool_value(|c| c.use_media_dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_media_dir(&mut self, value: bool) -> bool {
|
|
||||||
if self.media_dir() == value {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
self.use_media_dir = Some(value);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If `true` the home directory of this realm will be set up in ephemeral mode.
|
/// If `true` the home directory of this realm will be set up in ephemeral mode.
|
||||||
///
|
///
|
||||||
/// The ephemeral home directory is set up with the following steps:
|
/// The ephemeral home directory is set up with the following steps:
|
||||||
@@ -377,14 +308,6 @@ impl RealmConfig {
|
|||||||
self.bool_value(|c| c.use_ephemeral_home)
|
self.bool_value(|c| c.use_ephemeral_home)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_ephemeral_home(&mut self, value: bool) -> bool {
|
|
||||||
if self.ephemeral_home() == value {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
self.use_ephemeral_home = Some(value);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A list of subdirectories of /realms/realm-${name}/home to bind mount into realm
|
/// A list of subdirectories of /realms/realm-${name}/home to bind mount into realm
|
||||||
/// home directory when ephemeral-home is enabled.
|
/// home directory when ephemeral-home is enabled.
|
||||||
pub fn ephemeral_persistent_dirs(&self) -> Vec<String> {
|
pub fn ephemeral_persistent_dirs(&self) -> Vec<String> {
|
||||||
@@ -407,14 +330,6 @@ impl RealmConfig {
|
|||||||
self.bool_value(|c| c.use_sound)
|
self.bool_value(|c| c.use_sound)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_sound(&mut self, value: bool) -> bool {
|
|
||||||
if self.sound() == value {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
self.use_sound = Some(value);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If `true` access to the X11 server will be added to realm by bind mounting
|
/// If `true` access to the X11 server will be added to realm by bind mounting
|
||||||
/// directory /tmp/.X11-unix
|
/// directory /tmp/.X11-unix
|
||||||
pub fn x11(&self) -> bool {
|
pub fn x11(&self) -> bool {
|
||||||
@@ -423,28 +338,12 @@ impl RealmConfig {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_x11(&mut self, value: bool) -> bool {
|
|
||||||
if self.x11() == value {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
self.use_x11 = Some(value);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If `true` access to Wayland display will be permitted in realm by adding
|
/// If `true` access to Wayland display will be permitted in realm by adding
|
||||||
/// wayland socket /run/user/1000/wayland-0
|
/// wayland socket /run/user/1000/wayland-0
|
||||||
pub fn wayland(&self) -> bool {
|
pub fn wayland(&self) -> bool {
|
||||||
self.bool_value(|c| c.use_wayland)
|
self.bool_value(|c| c.use_wayland)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_wayland(&mut self, value: bool) -> bool {
|
|
||||||
if self.wayland() == value {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
self.use_wayland = Some(value);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The name of the wayland socket to use if `self.wayland()` is `true`
|
/// The name of the wayland socket to use if `self.wayland()` is `true`
|
||||||
/// defaults to wayland-0, will appear in the realm as wayland-0 regardless of value
|
/// defaults to wayland-0, will appear in the realm as wayland-0 regardless of value
|
||||||
pub fn wayland_socket(&self) -> &str {
|
pub fn wayland_socket(&self) -> &str {
|
||||||
@@ -457,19 +356,12 @@ impl RealmConfig {
|
|||||||
self.bool_value(|c| c.use_network)
|
self.bool_value(|c| c.use_network)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_network(&mut self, value: bool) -> bool {
|
|
||||||
if self.network() == value {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
self.use_network = Some(value);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The name of the network zone this realm will use if `self.network()` is `true`.
|
/// The name of the network zone this realm will use if `self.network()` is `true`.
|
||||||
pub fn network_zone(&self) -> &str {
|
pub fn network_zone(&self) -> &str {
|
||||||
self.str_value(|c| c.network_zone.as_ref()).unwrap_or(DEFAULT_ZONE)
|
self.str_value(|c| c.network_zone.as_ref()).unwrap_or(DEFAULT_ZONE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// If configured, this realm uses a fixed IP address on the zone subnet. The last
|
/// If configured, this realm uses a fixed IP address on the zone subnet. The last
|
||||||
/// octet of the network address for this realm will be set to the provided value.
|
/// octet of the network address for this realm will be set to the provided value.
|
||||||
pub fn reserved_ip(&self) -> Option<u8> {
|
pub fn reserved_ip(&self) -> Option<u8> {
|
||||||
@@ -530,6 +422,7 @@ impl RealmConfig {
|
|||||||
self.overlay = overlay.to_str_value().map(String::from)
|
self.overlay = overlay.to_str_value().map(String::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn netns(&self) -> Option<&str> {
|
pub fn netns(&self) -> Option<&str> {
|
||||||
self.str_value(|c| c.netns.as_ref())
|
self.str_value(|c| c.netns.as_ref())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,7 @@ use dbus::{Connection, BusType, ConnectionItem, Message, Path};
|
|||||||
use inotify::{Inotify, WatchMask, WatchDescriptor, Event};
|
use inotify::{Inotify, WatchMask, WatchDescriptor, Event};
|
||||||
|
|
||||||
pub enum RealmEvent {
|
pub enum RealmEvent {
|
||||||
Starting(Realm),
|
|
||||||
Started(Realm),
|
Started(Realm),
|
||||||
Stopping(Realm),
|
|
||||||
Stopped(Realm),
|
Stopped(Realm),
|
||||||
New(Realm),
|
New(Realm),
|
||||||
Removed(Realm),
|
Removed(Realm),
|
||||||
@@ -24,9 +22,7 @@ pub enum RealmEvent {
|
|||||||
impl Display for RealmEvent {
|
impl Display for RealmEvent {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
RealmEvent::Starting(ref realm) => write!(f, "RealmStarting({})", realm.name()),
|
|
||||||
RealmEvent::Started(ref realm) => write!(f, "RealmStarted({})", realm.name()),
|
RealmEvent::Started(ref realm) => write!(f, "RealmStarted({})", realm.name()),
|
||||||
RealmEvent::Stopping(ref realm) => write!(f, "RealmStopping({})", realm.name()),
|
|
||||||
RealmEvent::Stopped(ref realm) => write!(f, "RealmStopped({})", realm.name()),
|
RealmEvent::Stopped(ref realm) => write!(f, "RealmStopped({})", realm.name()),
|
||||||
RealmEvent::New(ref realm) => write!(f, "RealmNew({})", realm.name()),
|
RealmEvent::New(ref realm) => write!(f, "RealmNew({})", realm.name()),
|
||||||
RealmEvent::Removed(ref realm) => write!(f, "RealmRemoved({})", realm.name()),
|
RealmEvent::Removed(ref realm) => write!(f, "RealmRemoved({})", realm.name()),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::fmt::Write;
|
use std::fmt::{self,Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use crate::{Realm, Result, util, realm::network::NetworkConfig};
|
use crate::{Realm, Result, util, realm::network::NetworkConfig};
|
||||||
@@ -18,23 +18,6 @@ $EXTRA_FILE_OPTIONS
|
|||||||
|
|
||||||
";
|
";
|
||||||
|
|
||||||
// SYSTEMD_NSPAWN_SHARE_NS_IPC is a secret flag that allows sharing IPC namespace between
|
|
||||||
// nspawn container and host. This is needed so that the X11 MIT-SHM extension will work
|
|
||||||
// correctly. Sharing the IPC namespace is not ideal, but also not obviously harmful.
|
|
||||||
//
|
|
||||||
// If this patch was applied to Mutter, then the MIT-SHM extension could be disabled for
|
|
||||||
// XWayland which would break some applications and degrade performance of others.
|
|
||||||
//
|
|
||||||
// https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2136
|
|
||||||
//
|
|
||||||
// Setting QT_X11_NO_MITSHM=1 should at least prevent QT applications from crashing if
|
|
||||||
// extension is not available.
|
|
||||||
//
|
|
||||||
// Another approach would be to use LD_PRELOAD to disable visibility of the extension for
|
|
||||||
// applications inside of container:
|
|
||||||
//
|
|
||||||
// https://github.com/jessfraz/dockerfiles/issues/359#issuecomment-828714848
|
|
||||||
//
|
|
||||||
const REALM_SERVICE_TEMPLATE: &str = "\
|
const REALM_SERVICE_TEMPLATE: &str = "\
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Application Image $REALM_NAME instance
|
Description=Application Image $REALM_NAME instance
|
||||||
@@ -77,7 +60,7 @@ impl <'a> RealmLauncher <'a> {
|
|||||||
if config.kvm() {
|
if config.kvm() {
|
||||||
self.add_device("/dev/kvm");
|
self.add_device("/dev/kvm");
|
||||||
}
|
}
|
||||||
if config.fuse() || config.flatpak() {
|
if config.fuse() {
|
||||||
self.add_device("/dev/fuse");
|
self.add_device("/dev/fuse");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,10 +153,6 @@ impl <'a> RealmLauncher <'a> {
|
|||||||
writeln!(s, "BindReadOnly=/run/user/1000/{}:/run/user/host/wayland-0", config.wayland_socket())?;
|
writeln!(s, "BindReadOnly=/run/user/1000/{}:/run/user/host/wayland-0", config.wayland_socket())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.flatpak() {
|
|
||||||
writeln!(s, "BindReadOnly={}:/var/lib/flatpak", self.realm.base_path_file("flatpak").display())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
for bind in config.extra_bindmounts() {
|
for bind in config.extra_bindmounts() {
|
||||||
if Self::is_valid_bind_item(bind) {
|
if Self::is_valid_bind_item(bind) {
|
||||||
writeln!(s, "Bind={}", bind)?;
|
writeln!(s, "Bind={}", bind)?;
|
||||||
@@ -252,4 +231,9 @@ impl <'a> RealmLauncher <'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<fmt::Error> for crate::Error {
|
||||||
|
fn from(e: fmt::Error) -> Self {
|
||||||
|
format_err!("Error formatting string: {}", e).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,200 +0,0 @@
|
|||||||
use std::process::Command;
|
|
||||||
use crate::{Realm,Result};
|
|
||||||
|
|
||||||
pub struct LiveConfig<'a> {
|
|
||||||
realm: &'a Realm,
|
|
||||||
}
|
|
||||||
|
|
||||||
const LIVE_VARS: &[&str] = &[
|
|
||||||
"use-gpu", "use-gpu-card0", "use-wayland", "use-x11",
|
|
||||||
"use-sound", "use-shared-dir", "use-kvm", "use-media-dir",
|
|
||||||
"use-fuse", "use-flatpak"
|
|
||||||
];
|
|
||||||
|
|
||||||
const SYSTEMCTL_PATH: &str = "/usr/bin/systemctl";
|
|
||||||
const SYSTEMD_RUN_PATH: &str = "/usr/bin/systemd-run";
|
|
||||||
const MACHINECTL_PATH: &str = "/usr/bin/machinectl";
|
|
||||||
|
|
||||||
impl <'a> LiveConfig<'a> {
|
|
||||||
pub fn is_live_configurable(varname: &str) -> bool {
|
|
||||||
LIVE_VARS.contains(&varname)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new(realm: &'a Realm) -> Self {
|
|
||||||
LiveConfig {
|
|
||||||
realm
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn configure(&self, varname: &str, enabled: bool) -> Result<()> {
|
|
||||||
match varname {
|
|
||||||
"use-gpu" => self.configure_gpu(enabled),
|
|
||||||
"use-gpu-card0" => self.configure_gpu_card0(enabled),
|
|
||||||
"use-wayland" => self.configure_wayland(enabled),
|
|
||||||
"use-x11" => self.configure_x11(enabled),
|
|
||||||
"use-sound" => self.configure_sound(enabled),
|
|
||||||
"use-shared-dir" => self.configure_shared(enabled),
|
|
||||||
"use-kvm" => self.configure_kvm(enabled),
|
|
||||||
"use-media-dir" => self.configure_media(enabled),
|
|
||||||
"use-fuse" => self.configure_fuse(enabled),
|
|
||||||
"use-flatpak" => self.configure_flatpak(enabled),
|
|
||||||
_ => bail!("Unknown live configuration variable '{}'", varname),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn configure_gpu(&self, enabled: bool) -> Result<()> {
|
|
||||||
self.enable_device("/dev/dri/renderD128", enabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn configure_gpu_card0(&self, enabled: bool) -> Result<()> {
|
|
||||||
self.enable_device("/dev/dri/card0", enabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn configure_kvm(&self, enabled: bool) -> Result<()> {
|
|
||||||
self.enable_device("/dev/kvm", enabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn configure_fuse(&self, enabled: bool) -> Result<()> {
|
|
||||||
self.enable_device("/dev/fuse", enabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn configure_shared(&self, enabled: bool) -> Result<()> {
|
|
||||||
self.enable_bind_mount(
|
|
||||||
"/realms/Shared",
|
|
||||||
Some("/home/user/Shared"),
|
|
||||||
false,
|
|
||||||
enabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn configure_media(&self, enabled: bool) -> Result<()> {
|
|
||||||
self.enable_bind_mount(
|
|
||||||
"/run/media/citadel",
|
|
||||||
Some("/home/user/Media"),
|
|
||||||
false,
|
|
||||||
enabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn configure_sound(&self, enabled: bool) -> Result<()> {
|
|
||||||
self.enable_bind_mount(
|
|
||||||
"/run/user/1000/pulse",
|
|
||||||
Some("/run/user/host/pulse"),
|
|
||||||
true,
|
|
||||||
enabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn configure_wayland(&self, enabled: bool) -> Result<()> {
|
|
||||||
self.enable_bind_mount(
|
|
||||||
"/run/user/1000/wayland-0",
|
|
||||||
Some("/run/user/host/wayland-0"),
|
|
||||||
true,
|
|
||||||
enabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn configure_x11(&self, enabled: bool) -> Result<()> {
|
|
||||||
self.enable_bind_mount(
|
|
||||||
"/tmp/.X11-unix",
|
|
||||||
None,
|
|
||||||
true,
|
|
||||||
enabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn configure_flatpak(&self, enabled: bool) -> Result<()> {
|
|
||||||
let path = self.realm.base_path_file("flatpak")
|
|
||||||
.display()
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
self.enable_bind_mount(
|
|
||||||
&path,
|
|
||||||
Some("/var/lib/flatpak"),
|
|
||||||
true,
|
|
||||||
enabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enable_bind_mount(&self, path: &str, target: Option<&str>, readonly: bool, enabled: bool) -> Result<()> {
|
|
||||||
if enabled {
|
|
||||||
self.machinectl_bind(path, target, readonly)
|
|
||||||
} else if let Some(target) = target {
|
|
||||||
self.unmount(target, false)
|
|
||||||
} else {
|
|
||||||
self.unmount(path, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enable_device(&self, device_path: &str, enabled: bool) -> Result<()> {
|
|
||||||
if enabled {
|
|
||||||
self.machinectl_bind(device_path, None, false)?;
|
|
||||||
self.systemctl_device_allow(device_path)?;
|
|
||||||
} else {
|
|
||||||
self.unmount(device_path, true)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unmount(&self, path: &str, delete: bool) -> Result<()> {
|
|
||||||
// systemd-run --machine={name} umount {path}
|
|
||||||
self.systemd_run(&["umount", path])?;
|
|
||||||
if delete {
|
|
||||||
self.systemd_run(&["rm", path])?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn systemctl_device_allow(&self, device_path: &str) -> Result<()> {
|
|
||||||
let status = Command::new(SYSTEMCTL_PATH)
|
|
||||||
.arg("set-property")
|
|
||||||
.arg(format!("realm-{}.service", self.realm.name()))
|
|
||||||
.arg(format!("DeviceAllow={}", device_path))
|
|
||||||
.status()
|
|
||||||
.map_err(context!("failed to execute {}", SYSTEMCTL_PATH))?;
|
|
||||||
|
|
||||||
if !status.success() {
|
|
||||||
bail!("'systemctl set-property realm-{}.service DeviceAllow={}' command did not complete successfully status: {:?}", self.realm.name(), device_path, status.code());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn machinectl_bind(&self, path: &str, target: Option<&str>, readonly: bool) -> Result<()> {
|
|
||||||
let mut cmd = Command::new(MACHINECTL_PATH);
|
|
||||||
cmd.arg("--mkdir");
|
|
||||||
|
|
||||||
if readonly {
|
|
||||||
cmd.arg("--read-only");
|
|
||||||
}
|
|
||||||
cmd.arg("bind")
|
|
||||||
.arg(self.realm.name())
|
|
||||||
.arg(path);
|
|
||||||
|
|
||||||
if let Some(target) = target {
|
|
||||||
cmd.arg(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
let status = cmd.status()
|
|
||||||
.map_err(context!("failed to execute {}", MACHINECTL_PATH))?;
|
|
||||||
|
|
||||||
|
|
||||||
if !status.success() {
|
|
||||||
bail!("machinectl bind {} {} {:?} did not complete successfully status: {:?}", self.realm.name(), path, target, status.code());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fn systemd_run(&self, args: &[&str]) -> Result<()> {
|
|
||||||
let status = Command::new(SYSTEMD_RUN_PATH)
|
|
||||||
.arg("--quiet")
|
|
||||||
.arg(format!("--machine={}", self.realm.name()))
|
|
||||||
.args(args)
|
|
||||||
.status()
|
|
||||||
.map_err(context!("failed to execute {}", MACHINECTL_PATH))?;
|
|
||||||
|
|
||||||
|
|
||||||
if !status.success() {
|
|
||||||
bail!("systemd-run --machine={} command did not complete successfully args: {:?} status: {:?}", self.realm.name(), args, status.code());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,8 +4,6 @@ use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
|||||||
use posix_acl::{ACL_EXECUTE, ACL_READ, PosixACL, Qualifier};
|
use posix_acl::{ACL_EXECUTE, ACL_READ, PosixACL, Qualifier};
|
||||||
|
|
||||||
use crate::{Mountpoint, Result, Realms, RealmFS, Realm, util};
|
use crate::{Mountpoint, Result, Realms, RealmFS, Realm, util};
|
||||||
use crate::flatpak::GnomeSoftwareLauncher;
|
|
||||||
use crate::flatpak::setup::FlatpakSetup;
|
|
||||||
use crate::realm::pidmapper::{PidLookupResult, PidMapper};
|
use crate::realm::pidmapper::{PidLookupResult, PidMapper};
|
||||||
use crate::realmfs::realmfs_set::RealmFSSet;
|
use crate::realmfs::realmfs_set::RealmFSSet;
|
||||||
|
|
||||||
@@ -23,7 +21,6 @@ struct Inner {
|
|||||||
events: RealmEventListener,
|
events: RealmEventListener,
|
||||||
realms: Realms,
|
realms: Realms,
|
||||||
realmfs_set: RealmFSSet,
|
realmfs_set: RealmFSSet,
|
||||||
pid_mapper: PidMapper,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Inner {
|
impl Inner {
|
||||||
@@ -31,8 +28,7 @@ impl Inner {
|
|||||||
let events = RealmEventListener::new();
|
let events = RealmEventListener::new();
|
||||||
let realms = Realms::load()?;
|
let realms = Realms::load()?;
|
||||||
let realmfs_set = RealmFSSet::load()?;
|
let realmfs_set = RealmFSSet::load()?;
|
||||||
let pid_mapper = PidMapper::new()?;
|
Ok(Inner { events, realms, realmfs_set })
|
||||||
Ok(Inner { events, realms, realmfs_set, pid_mapper })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,13 +190,7 @@ impl RealmManager {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
info!("Starting realm {}", realm.name());
|
info!("Starting realm {}", realm.name());
|
||||||
self.inner().events.send_event(RealmEvent::Starting(realm.clone()));
|
self._start_realm(realm, &mut HashSet::new())?;
|
||||||
if let Err(err) = self._start_realm(realm, &mut HashSet::new()) {
|
|
||||||
self.inner().events.send_event(RealmEvent::Stopped(realm.clone()));
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.inner().events.send_event(RealmEvent::Started(realm.clone()));
|
|
||||||
|
|
||||||
if !Realms::is_some_realm_current() {
|
if !Realms::is_some_realm_current() {
|
||||||
self.inner_mut().realms.set_realm_current(realm)
|
self.inner_mut().realms.set_realm_current(realm)
|
||||||
@@ -240,10 +230,6 @@ impl RealmManager {
|
|||||||
self.ensure_run_media_directory()?;
|
self.ensure_run_media_directory()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if realm.config().flatpak() {
|
|
||||||
FlatpakSetup::new(realm).setup()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.systemd.start_realm(realm, &rootfs)?;
|
self.systemd.start_realm(realm, &rootfs)?;
|
||||||
|
|
||||||
self.create_realm_namefile(realm)?;
|
self.create_realm_namefile(realm)?;
|
||||||
@@ -282,15 +268,6 @@ impl RealmManager {
|
|||||||
self.run_in_realm(realm, &["/usr/bin/ln", "-s", "/run/user/host/wayland-0", "/run/user/1000/wayland-0"], false)
|
self.run_in_realm(realm, &["/usr/bin/ln", "-s", "/run/user/host/wayland-0", "/run/user/1000/wayland-0"], false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop_gnome_software_sandbox(&self, realm: &Realm) -> Result<()> {
|
|
||||||
let launcher = GnomeSoftwareLauncher::new(realm.clone())?;
|
|
||||||
if launcher.is_running() {
|
|
||||||
info!("Stopping GNOME Software sandbox for {}", realm.name());
|
|
||||||
launcher.quit()?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stop_realm(&self, realm: &Realm) -> Result<()> {
|
pub fn stop_realm(&self, realm: &Realm) -> Result<()> {
|
||||||
if !realm.is_active() {
|
if !realm.is_active() {
|
||||||
info!("ignoring stop request on realm '{}' which is not running", realm.name());
|
info!("ignoring stop request on realm '{}' which is not running", realm.name());
|
||||||
@@ -298,21 +275,10 @@ impl RealmManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
info!("Stopping realm {}", realm.name());
|
info!("Stopping realm {}", realm.name());
|
||||||
self.inner().events.send_event(RealmEvent::Stopping(realm.clone()));
|
|
||||||
|
|
||||||
if realm.config().flatpak() {
|
|
||||||
if let Err(err) = self.stop_gnome_software_sandbox(realm) {
|
|
||||||
warn!("Error stopping GNOME Software sandbox: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
realm.set_active(false);
|
realm.set_active(false);
|
||||||
if let Err(err) = self.systemd.stop_realm(realm) {
|
self.systemd.stop_realm(realm)?;
|
||||||
self.inner().events.send_event(RealmEvent::Stopped(realm.clone()));
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
realm.cleanup_rootfs();
|
realm.cleanup_rootfs();
|
||||||
self.inner().events.send_event(RealmEvent::Stopped(realm.clone()));
|
|
||||||
|
|
||||||
if realm.is_current() {
|
if realm.is_current() {
|
||||||
self.choose_some_current_realm();
|
self.choose_some_current_realm();
|
||||||
@@ -369,8 +335,8 @@ impl RealmManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn realm_by_pid(&self, pid: u32) -> PidLookupResult {
|
pub fn realm_by_pid(&self, pid: u32) -> PidLookupResult {
|
||||||
let realms = self.realm_list();
|
let mapper = PidMapper::new(self.active_realms(false));
|
||||||
self.inner_mut().pid_mapper.lookup_pid(pid as libc::pid_t, realms)
|
mapper.lookup_pid(pid as libc::pid_t)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rescan_realms(&self) -> Result<(Vec<Realm>,Vec<Realm>)> {
|
pub fn rescan_realms(&self) -> Result<(Vec<Realm>,Vec<Realm>)> {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
|
||||||
pub(crate) mod overlay;
|
pub(crate) mod overlay;
|
||||||
pub(crate) mod config;
|
pub(crate) mod config;
|
||||||
pub(crate) mod liveconfig;
|
|
||||||
pub(crate) mod realms;
|
pub(crate) mod realms;
|
||||||
pub(crate) mod manager;
|
pub(crate) mod manager;
|
||||||
#[allow(clippy::module_inception)]
|
#[allow(clippy::module_inception)]
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use procfs::process::Process;
|
use procfs::process::Process;
|
||||||
use crate::{Result, Realm};
|
use crate::Realm;
|
||||||
use crate::flatpak::{SANDBOX_STATUS_FILE_DIRECTORY, SandboxStatus};
|
|
||||||
|
|
||||||
pub enum PidLookupResult {
|
pub enum PidLookupResult {
|
||||||
Unknown,
|
Unknown,
|
||||||
@@ -10,15 +9,14 @@ pub enum PidLookupResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct PidMapper {
|
pub struct PidMapper {
|
||||||
sandbox_status: SandboxStatus,
|
active_realms: Vec<Realm>,
|
||||||
my_pid_ns_id: Option<u64>,
|
my_pid_ns_id: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PidMapper {
|
impl PidMapper {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new(active_realms: Vec<Realm>) -> Self {
|
||||||
let sandbox_status = SandboxStatus::load(SANDBOX_STATUS_FILE_DIRECTORY)?;
|
|
||||||
let my_pid_ns_id = Self::self_pid_namespace_id();
|
let my_pid_ns_id = Self::self_pid_namespace_id();
|
||||||
Ok(PidMapper { sandbox_status, my_pid_ns_id })
|
PidMapper { active_realms, my_pid_ns_id }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_process(pid: libc::pid_t) -> Option<Process> {
|
fn read_process(pid: libc::pid_t) -> Option<Process> {
|
||||||
@@ -74,30 +72,7 @@ impl PidMapper {
|
|||||||
Self::read_process(ppid)
|
Self::read_process(ppid)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn refresh_sandbox_status(&mut self) -> Result<()> {
|
pub fn lookup_pid(&self, pid: libc::pid_t) -> PidLookupResult {
|
||||||
if self.sandbox_status.need_reload() {
|
|
||||||
self.sandbox_status.reload()?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_sandbox_realms(&mut self, pid_ns: u64, realms: &[Realm]) -> Option<Realm> {
|
|
||||||
if let Err(err) = self.refresh_sandbox_status() {
|
|
||||||
warn!("error reloading sandbox status directory: {}", err);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
for r in realms {
|
|
||||||
if let Some(status) = self.sandbox_status.realm_status(r) {
|
|
||||||
if status.pid_namespace() == pid_ns {
|
|
||||||
return Some(r.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lookup_pid(&mut self, pid: libc::pid_t, realms: Vec<Realm>) -> PidLookupResult {
|
|
||||||
const MAX_PARENT_SEARCH: i32 = 8;
|
const MAX_PARENT_SEARCH: i32 = 8;
|
||||||
let mut n = 0;
|
let mut n = 0;
|
||||||
|
|
||||||
@@ -117,17 +92,13 @@ impl PidMapper {
|
|||||||
return PidLookupResult::Citadel;
|
return PidLookupResult::Citadel;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(realm) = realms.iter()
|
if let Some(realm) = self.active_realms.iter()
|
||||||
.find(|r| r.is_active() && r.has_pid_ns(pid_ns_id))
|
.find(|r| r.has_pid_ns(pid_ns_id))
|
||||||
.cloned()
|
.cloned()
|
||||||
{
|
{
|
||||||
return PidLookupResult::Realm(realm)
|
return PidLookupResult::Realm(realm)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(r) = self.search_sandbox_realms(pid_ns_id, &realms) {
|
|
||||||
return PidLookupResult::Realm(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
proc = match Self::parent_process(proc) {
|
proc = match Self::parent_process(proc) {
|
||||||
Some(proc) => proc,
|
Some(proc) => proc,
|
||||||
None => return PidLookupResult::Unknown,
|
None => return PidLookupResult::Unknown,
|
||||||
@@ -137,4 +108,5 @@ impl PidMapper {
|
|||||||
}
|
}
|
||||||
PidLookupResult::Unknown
|
PidLookupResult::Unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,20 +169,6 @@ impl Realm {
|
|||||||
self.inner.write().unwrap()
|
self.inner.write().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn start(&self) -> Result<()> {
|
|
||||||
warn!("Realm({})::start()", self.name());
|
|
||||||
self.manager().start_realm(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stop(&self) -> Result<()> {
|
|
||||||
self.manager().stop_realm(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_current(&self) -> Result<()> {
|
|
||||||
self.manager().set_current_realm(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_active(&self) -> bool {
|
pub fn is_active(&self) -> bool {
|
||||||
self.inner_mut().is_active()
|
self.inner_mut().is_active()
|
||||||
}
|
}
|
||||||
@@ -293,9 +279,6 @@ impl Realm {
|
|||||||
symlink::write(&rootfs, self.rootfs_symlink(), false)?;
|
symlink::write(&rootfs, self.rootfs_symlink(), false)?;
|
||||||
symlink::write(mountpoint.path(), self.realmfs_mountpoint_symlink(), false)?;
|
symlink::write(mountpoint.path(), self.realmfs_mountpoint_symlink(), false)?;
|
||||||
symlink::write(self.base_path().join("home"), self.run_path().join("home"), false)?;
|
symlink::write(self.base_path().join("home"), self.run_path().join("home"), false)?;
|
||||||
if self.config().flatpak() {
|
|
||||||
symlink::write(self.base_path().join("flatpak"), self.run_path().join("flatpak"), false)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(rootfs)
|
Ok(rootfs)
|
||||||
}
|
}
|
||||||
@@ -317,9 +300,6 @@ impl Realm {
|
|||||||
Self::remove_symlink(self.realmfs_mountpoint_symlink());
|
Self::remove_symlink(self.realmfs_mountpoint_symlink());
|
||||||
Self::remove_symlink(self.rootfs_symlink());
|
Self::remove_symlink(self.rootfs_symlink());
|
||||||
Self::remove_symlink(self.run_path().join("home"));
|
Self::remove_symlink(self.run_path().join("home"));
|
||||||
if self.config().flatpak() {
|
|
||||||
Self::remove_symlink(self.run_path().join("flatpak"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = fs::remove_dir(self.run_path()) {
|
if let Err(e) = fs::remove_dir(self.run_path()) {
|
||||||
warn!("failed to remove run directory {}: {}", self.run_path().display(), e);
|
warn!("failed to remove run directory {}: {}", self.run_path().display(), e);
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ impl Realms {
|
|||||||
pub fn delete_realm(&mut self, name: &str, save_home: bool) -> Result<()> {
|
pub fn delete_realm(&mut self, name: &str, save_home: bool) -> Result<()> {
|
||||||
let _lock = Self::realmslock()?;
|
let _lock = Self::realmslock()?;
|
||||||
|
|
||||||
let realm = match self.by_name(name) {
|
let realm = match self.realms.take(name) {
|
||||||
Some(realm) => realm,
|
Some(realm) => realm,
|
||||||
None => bail!("Cannot remove realm '{}' because it doesn't seem to exist", name),
|
None => bail!("Cannot remove realm '{}' because it doesn't seem to exist", name),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,28 +31,6 @@ impl Systemd {
|
|||||||
if realm.config().ephemeral_home() {
|
if realm.config().ephemeral_home() {
|
||||||
self.setup_ephemeral_home(realm)?;
|
self.setup_ephemeral_home(realm)?;
|
||||||
}
|
}
|
||||||
if realm.config().flatpak() {
|
|
||||||
self.setup_flatpak_workaround(realm)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// What even is this??
|
|
||||||
//
|
|
||||||
// Good question.
|
|
||||||
//
|
|
||||||
// https://bugzilla.redhat.com/show_bug.cgi?id=2210335#c10
|
|
||||||
//
|
|
||||||
fn setup_flatpak_workaround(&self, realm: &Realm) -> Result<()> {
|
|
||||||
let commands = &[
|
|
||||||
vec!["/usr/bin/mount", "-m", "-t","proc", "proc", "/run/flatpak-workaround/proc"],
|
|
||||||
vec!["/usr/bin/chmod", "700", "/run/flatpak-workaround"],
|
|
||||||
];
|
|
||||||
|
|
||||||
for cmd in commands {
|
|
||||||
Self::machinectl_shell(realm, cmd, "root", false, true)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +91,7 @@ impl Systemd {
|
|||||||
.arg(name)
|
.arg(name)
|
||||||
.status()
|
.status()
|
||||||
.map(|status| status.success())
|
.map(|status| status.success())
|
||||||
.map_err(context!("failed to execute {}", SYSTEMCTL_PATH))?;
|
.map_err(context!("failed to execute {}", MACHINECTL_PATH))?;
|
||||||
Ok(ok)
|
Ok(ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,174 +0,0 @@
|
|||||||
use std::cell::Cell;
|
|
||||||
use std::fmt::Write;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
use crate::realm::BridgeAllocator;
|
|
||||||
use crate::{util, Result};
|
|
||||||
|
|
||||||
const NSPAWN_FILE_TEMPLATE: &str = "\
|
|
||||||
[Exec]
|
|
||||||
Boot=true
|
|
||||||
$NETWORK_CONFIG
|
|
||||||
|
|
||||||
[Files]
|
|
||||||
BindReadOnly=/storage/citadel-state/resolv.conf:/etc/resolv.conf
|
|
||||||
|
|
||||||
$BIND_MOUNTS
|
|
||||||
";
|
|
||||||
|
|
||||||
const SERVICE_TEMPLATE: &str = "\
|
|
||||||
[Unit]
|
|
||||||
Description=Update RealmFS $MACHINE_NAME instance
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
|
|
||||||
DevicePolicy=closed
|
|
||||||
|
|
||||||
ExecStart=/usr/bin/systemd-nspawn --quiet --console=passive --notify-ready=yes --keep-unit --machine=$MACHINE_NAME --link-journal=auto --directory=$ROOTFS
|
|
||||||
|
|
||||||
KillMode=mixed
|
|
||||||
Type=notify
|
|
||||||
RestartForceExitStatus=133
|
|
||||||
SuccessExitStatus=133
|
|
||||||
";
|
|
||||||
|
|
||||||
const SYSTEMCTL_PATH: &str = "/usr/bin/systemctl";
|
|
||||||
const SYSTEMD_NSPAWN_PATH: &str = "/run/systemd/nspawn";
|
|
||||||
const SYSTEMD_UNIT_PATH: &str = "/run/systemd/system";
|
|
||||||
|
|
||||||
/// Launcher for RealmFS update containers
|
|
||||||
pub struct RealmFSUpdateLauncher {
|
|
||||||
machine_name: String,
|
|
||||||
shared_directory: bool,
|
|
||||||
running: Cell<bool>,
|
|
||||||
rootfs: PathBuf,
|
|
||||||
service_path: PathBuf,
|
|
||||||
nspawn_path: PathBuf,
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RealmFSUpdateLauncher {
|
|
||||||
|
|
||||||
fn new(machine_name: &str, rootfs: &Path, shared_directory: bool) -> Self {
|
|
||||||
let machine_name = machine_name.to_string();
|
|
||||||
let running = Cell::new(false);
|
|
||||||
let rootfs = rootfs.to_owned();
|
|
||||||
let service_path = PathBuf::from(SYSTEMD_UNIT_PATH).join(format!("realmfs-{machine_name}.service"));
|
|
||||||
let nspawn_path= PathBuf::from(SYSTEMD_NSPAWN_PATH).join(format!("{machine_name}.nspawn"));
|
|
||||||
RealmFSUpdateLauncher {
|
|
||||||
machine_name, shared_directory, running, rootfs, service_path, nspawn_path,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn launch_update_container(machine_name: &str, rootfs: &Path, shared_directory: bool) -> Result<Self> {
|
|
||||||
let launcher = Self::new(machine_name, rootfs, shared_directory);
|
|
||||||
launcher.start_container()?;
|
|
||||||
Ok(launcher)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn systemctl_start(&self) -> Result<bool> {
|
|
||||||
self.run_systemctl("start")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn systemctl_stop(&self) -> Result<bool> {
|
|
||||||
self.run_systemctl("stop")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_systemctl(&self, op: &str) -> Result<bool> {
|
|
||||||
let service_name = format!("realmfs-{}", self.machine_name);
|
|
||||||
let ok = Command::new(SYSTEMCTL_PATH)
|
|
||||||
.arg(op)
|
|
||||||
.arg(service_name)
|
|
||||||
.status()
|
|
||||||
.map(|status| status.success())
|
|
||||||
.map_err(context!("failed to execute {}", SYSTEMCTL_PATH))?;
|
|
||||||
Ok(ok)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_container(&self) -> Result<()> {
|
|
||||||
self.write_launch_config_files()?;
|
|
||||||
let ok = self.systemctl_start()?;
|
|
||||||
self.running.set(ok);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stop_container(&self) -> Result<()> {
|
|
||||||
if self.running.replace(false) {
|
|
||||||
self.systemctl_stop()?;
|
|
||||||
self.remove_launch_config_files()?;
|
|
||||||
// XXX remove IP address allocation?
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_launch_config_files(&self) -> Result<()> {
|
|
||||||
util::remove_file(&self.nspawn_path)?;
|
|
||||||
util::remove_file(&self.service_path)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_nspawn_file(&self) -> Result<String> {
|
|
||||||
Ok(NSPAWN_FILE_TEMPLATE
|
|
||||||
.replace("$BIND_MOUNTS", &self.generate_bind_mounts()?)
|
|
||||||
.replace("$NETWORK_CONFIG", &self.generate_network_config()?))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_service_file(&self) -> String {
|
|
||||||
let rootfs = self.rootfs.display().to_string();
|
|
||||||
SERVICE_TEMPLATE
|
|
||||||
.replace("$MACHINE_NAME", &self.machine_name)
|
|
||||||
.replace("$ROOTFS", &rootfs)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write the string `content` to file `path`. If the directory does
|
|
||||||
/// not already exist, create it.
|
|
||||||
fn write_launch_config_file(&self, path: &Path, content: &str) -> Result<()> {
|
|
||||||
match path.parent() {
|
|
||||||
Some(parent) => util::create_dir(parent)?,
|
|
||||||
None => bail!("config file path {} has no parent?", path.display()),
|
|
||||||
};
|
|
||||||
util::write_file(path, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_bind_mounts(&self) -> Result<String> {
|
|
||||||
let mut s = String::new();
|
|
||||||
if self.shared_directory && Path::new("/realms/Shared").exists() {
|
|
||||||
writeln!(s, "Bind=/realms/Shared:/run/Shared")?;
|
|
||||||
}
|
|
||||||
Ok(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_network_config(&self) -> Result<String> {
|
|
||||||
let mut s = String::new();
|
|
||||||
|
|
||||||
let mut alloc = BridgeAllocator::default_bridge()?;
|
|
||||||
let addr = alloc.allocate_address_for(&self.machine_name)?;
|
|
||||||
let gw = alloc.gateway();
|
|
||||||
|
|
||||||
writeln!(s, "Environment=IFCONFIG_IP={}", addr)?;
|
|
||||||
writeln!(s, "Environment=IFCONFIG_GW={}", gw)?;
|
|
||||||
writeln!(s, "[Network]")?;
|
|
||||||
writeln!(s, "Zone=clear")?;
|
|
||||||
|
|
||||||
Ok(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_launch_config_files(&self) -> Result<()> {
|
|
||||||
let nspawn_content = self.generate_nspawn_file()?;
|
|
||||||
self.write_launch_config_file(&self.nspawn_path, &nspawn_content)?;
|
|
||||||
|
|
||||||
let service_content = self.generate_service_file();
|
|
||||||
self.write_launch_config_file(&self.service_path, &service_content)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for RealmFSUpdateLauncher {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if let Err(err) = self.stop_container() {
|
|
||||||
warn!("Error stopping RealmFS update container: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
pub(crate) mod resizer;
|
pub(crate) mod resizer;
|
||||||
mod mountpoint;
|
mod mountpoint;
|
||||||
pub(crate) mod update;
|
mod update;
|
||||||
pub(crate) mod realmfs_set;
|
pub(crate) mod realmfs_set;
|
||||||
#[allow(clippy::module_inception)]
|
#[allow(clippy::module_inception)]
|
||||||
mod realmfs;
|
mod realmfs;
|
||||||
mod launcher;
|
|
||||||
|
|
||||||
pub use self::realmfs::RealmFS;
|
pub use self::realmfs::RealmFS;
|
||||||
pub use self::mountpoint::Mountpoint;
|
pub use self::mountpoint::Mountpoint;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use std::sync::{Arc, Weak, RwLock};
|
|||||||
|
|
||||||
use crate::{ImageHeader, MetaInfo, Result, KeyRing, KeyPair, util, RealmManager, PublicKey, ResizeSize};
|
use crate::{ImageHeader, MetaInfo, Result, KeyRing, KeyPair, util, RealmManager, PublicKey, ResizeSize};
|
||||||
use crate::realmfs::resizer::Superblock;
|
use crate::realmfs::resizer::Superblock;
|
||||||
use crate::realmfs::update::RealmFSUpdate;
|
use crate::realmfs::update::Update;
|
||||||
use super::mountpoint::Mountpoint;
|
use super::mountpoint::Mountpoint;
|
||||||
|
|
||||||
// Maximum length of a RealmFS name
|
// Maximum length of a RealmFS name
|
||||||
@@ -266,13 +266,8 @@ impl RealmFS {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&self) -> Result<RealmFSUpdate> {
|
|
||||||
let update = RealmFSUpdate::create(self)?;
|
|
||||||
Ok(update)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn interactive_update(&self, scheme: Option<&str>) -> Result<()> {
|
pub fn interactive_update(&self, scheme: Option<&str>) -> Result<()> {
|
||||||
let mut update = RealmFSUpdate::create(self)?;
|
let mut update = Update::create(self)?;
|
||||||
update.run_interactive_update(scheme)
|
update.run_interactive_update(scheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +276,10 @@ impl RealmFS {
|
|||||||
let pubkey = if self.metainfo().channel() == RealmFS::USER_KEYNAME {
|
let pubkey = if self.metainfo().channel() == RealmFS::USER_KEYNAME {
|
||||||
self.sealing_keys()?.public_key()
|
self.sealing_keys()?.public_key()
|
||||||
} else {
|
} else {
|
||||||
self.header().public_key()?
|
match self.header().public_key()? {
|
||||||
|
Some(pubkey) => pubkey,
|
||||||
|
None => bail!("No public key available for channel {}", self.metainfo().channel()),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
Ok(pubkey)
|
Ok(pubkey)
|
||||||
}
|
}
|
||||||
@@ -390,14 +388,14 @@ impl RealmFS {
|
|||||||
|
|
||||||
pub fn resize_grow_to(&self, size: ResizeSize) -> Result<()> {
|
pub fn resize_grow_to(&self, size: ResizeSize) -> Result<()> {
|
||||||
info!("Resizing to {} blocks", size.nblocks());
|
info!("Resizing to {} blocks", size.nblocks());
|
||||||
let mut update = RealmFSUpdate::create(self)?;
|
let mut update = Update::create(self)?;
|
||||||
update.grow_to(size);
|
update.grow_to(size);
|
||||||
update.resize()
|
update.resize()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize_grow_by(&self, size: ResizeSize) -> Result<()> {
|
pub fn resize_grow_by(&self, size: ResizeSize) -> Result<()> {
|
||||||
info!("Resizing to an increase of {} blocks", size.nblocks());
|
info!("Resizing to an increase of {} blocks", size.nblocks());
|
||||||
let mut update = RealmFSUpdate::create(self)?;
|
let mut update = Update::create(self)?;
|
||||||
update.grow_by(size);
|
update.grow_by(size);
|
||||||
update.resize()
|
update.resize()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ impl ResizeSize {
|
|||||||
|
|
||||||
pub fn gigs(n: usize) -> Self {
|
pub fn gigs(n: usize) -> Self {
|
||||||
ResizeSize(BLOCKS_PER_GIG * n)
|
ResizeSize(BLOCKS_PER_GIG * n)
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
pub fn megs(n: usize) -> Self {
|
pub fn megs(n: usize) -> Self {
|
||||||
ResizeSize(BLOCKS_PER_MEG * n)
|
ResizeSize(BLOCKS_PER_MEG * n)
|
||||||
}
|
}
|
||||||
@@ -45,8 +45,8 @@ impl ResizeSize {
|
|||||||
self.0 / BLOCKS_PER_MEG
|
self.0 / BLOCKS_PER_MEG
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If the RealmFS has less than `AUTO_RESIZE_MINIMUM_FREE` blocks free then choose a new
|
/// If the RealmFS needs to be resized to a larger size, returns the
|
||||||
/// size to resize the filesystem to and return it. Otherwise, return `None`
|
/// recommended size.
|
||||||
pub fn auto_resize_size(realmfs: &RealmFS) -> Option<ResizeSize> {
|
pub fn auto_resize_size(realmfs: &RealmFS) -> Option<ResizeSize> {
|
||||||
let sb = match Superblock::load(realmfs.path(), 4096) {
|
let sb = match Superblock::load(realmfs.path(), 4096) {
|
||||||
Ok(sb) => sb,
|
Ok(sb) => sb,
|
||||||
@@ -56,37 +56,22 @@ impl ResizeSize {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
sb.free_block_count();
|
||||||
let free_blocks = sb.free_block_count() as usize;
|
let free_blocks = sb.free_block_count() as usize;
|
||||||
if free_blocks >= AUTO_RESIZE_MINIMUM_FREE.nblocks() {
|
if free_blocks < AUTO_RESIZE_MINIMUM_FREE.nblocks() {
|
||||||
return None;
|
let metainfo_nblocks = realmfs.metainfo().nblocks() + 1;
|
||||||
}
|
let increase_multiple = metainfo_nblocks / AUTO_RESIZE_INCREASE_SIZE.nblocks();
|
||||||
|
let grow_size = (increase_multiple + 1) * AUTO_RESIZE_INCREASE_SIZE.nblocks();
|
||||||
let metainfo_nblocks = realmfs.metainfo().nblocks();
|
let mask = grow_size - 1;
|
||||||
|
let grow_blocks = (free_blocks + mask) & !mask;
|
||||||
if metainfo_nblocks >= AUTO_RESIZE_INCREASE_SIZE.nblocks() {
|
Some(ResizeSize::blocks(grow_blocks))
|
||||||
return Some(ResizeSize::blocks(metainfo_nblocks + AUTO_RESIZE_INCREASE_SIZE.nblocks()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// If current size is under 4GB (AUTO_RESIZE_INCREASE_SIZE) and raising size to 4GB will create more than the
|
|
||||||
// minimum free space (1GB) then just do that.
|
|
||||||
if free_blocks + (AUTO_RESIZE_INCREASE_SIZE.nblocks() - metainfo_nblocks) >= AUTO_RESIZE_MINIMUM_FREE.nblocks() {
|
|
||||||
Some(AUTO_RESIZE_INCREASE_SIZE)
|
|
||||||
} else {
|
} else {
|
||||||
// Otherwise for original size under 4GB, since raising to 4GB is not enough,
|
None
|
||||||
// raise size to 8GB
|
|
||||||
Some(ResizeSize::blocks(AUTO_RESIZE_INCREASE_SIZE.nblocks() * 2))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const SUPERBLOCK_SIZE: usize = 1024;
|
const SUPERBLOCK_SIZE: usize = 1024;
|
||||||
|
|
||||||
/// An EXT4 superblock structure.
|
|
||||||
///
|
|
||||||
/// A class for reading the first superblock from an EXT4 filesystem
|
|
||||||
/// and parsing the Free Block Count field. No other fields are parsed
|
|
||||||
/// since this is the only information needed for the resize operation.
|
|
||||||
///
|
|
||||||
pub struct Superblock([u8; SUPERBLOCK_SIZE]);
|
pub struct Superblock([u8; SUPERBLOCK_SIZE]);
|
||||||
|
|
||||||
impl Superblock {
|
impl Superblock {
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ use crate::util::is_euid_root;
|
|||||||
use crate::terminal::TerminalRestorer;
|
use crate::terminal::TerminalRestorer;
|
||||||
use crate::verity::Verity;
|
use crate::verity::Verity;
|
||||||
|
|
||||||
use super::launcher::RealmFSUpdateLauncher;
|
|
||||||
|
|
||||||
const BLOCK_SIZE: usize = 4096;
|
const BLOCK_SIZE: usize = 4096;
|
||||||
|
|
||||||
// The maximum number of backup copies the rotate() method will create
|
// The maximum number of backup copies the rotate() method will create
|
||||||
@@ -23,41 +21,36 @@ const RESIZE2FS: &str = "resize2fs";
|
|||||||
|
|
||||||
/// Manages the process of updating or resizing a `RealmFS` image file.
|
/// Manages the process of updating or resizing a `RealmFS` image file.
|
||||||
///
|
///
|
||||||
pub struct RealmFSUpdate {
|
pub struct Update<'a> {
|
||||||
realmfs: RealmFS, // RealmFS being updated
|
realmfs: &'a RealmFS, // RealmFS being updated
|
||||||
name: String, // name for nspawn instance
|
name: String, // name for nspawn instance
|
||||||
target: PathBuf, // Path to the update copy of realmfs image
|
target: PathBuf, // Path to the update copy of realmfs image
|
||||||
mountpath: PathBuf, // Path at which update copy is mounted
|
mountpath: PathBuf, // Path at which update copy is mounted
|
||||||
container: Option<RealmFSUpdateLauncher>,
|
|
||||||
_lock: FileLock,
|
_lock: FileLock,
|
||||||
resize: Option<ResizeSize>, // If the image needs to be resized, the resize size is stored here
|
resize: Option<ResizeSize>, // If the image needs to be resized, the resize size is stored here
|
||||||
network_allocated: bool,
|
network_allocated: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RealmFSUpdate {
|
impl <'a> Update<'a> {
|
||||||
fn new(realmfs: RealmFS, lock: FileLock) -> Self {
|
fn new(realmfs: &'a RealmFS, lock: FileLock) -> Self {
|
||||||
|
|
||||||
let metainfo = realmfs.metainfo();
|
let metainfo = realmfs.metainfo();
|
||||||
let tag = metainfo.verity_tag();
|
let tag = metainfo.verity_tag();
|
||||||
let mountpath = Path::new(RealmFS::RUN_DIRECTORY)
|
let mountpath = Path::new(RealmFS::RUN_DIRECTORY)
|
||||||
.join(format!("realmfs-{}-{}.update", realmfs.name(), tag));
|
.join(format!("realmfs-{}-{}.update", realmfs.name(), tag));
|
||||||
|
|
||||||
let name = format!("{}-{}-update", realmfs.name(), tag);
|
Update {
|
||||||
let resize = ResizeSize::auto_resize_size(&realmfs);
|
|
||||||
let target = realmfs.path().with_extension("update");
|
|
||||||
RealmFSUpdate {
|
|
||||||
realmfs,
|
realmfs,
|
||||||
name,
|
name: format!("{}-{}-update", realmfs.name(), tag),
|
||||||
target,
|
target: realmfs.path().with_extension("update"),
|
||||||
mountpath,
|
mountpath,
|
||||||
_lock: lock,
|
_lock: lock,
|
||||||
container: None,
|
resize: ResizeSize::auto_resize_size(realmfs),
|
||||||
resize,
|
|
||||||
network_allocated: false,
|
network_allocated: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create(realmfs: &RealmFS) -> Result<Self> {
|
pub fn create(realmfs: &'a RealmFS) -> Result<Self> {
|
||||||
let lock = FileLock::nonblocking_acquire(realmfs.path().with_extension("lock"))?
|
let lock = FileLock::nonblocking_acquire(realmfs.path().with_extension("lock"))?
|
||||||
.ok_or(format_err!("Unable to obtain file lock to update realmfs image: {}", realmfs.name()))?;
|
.ok_or(format_err!("Unable to obtain file lock to update realmfs image: {}", realmfs.name()))?;
|
||||||
|
|
||||||
@@ -65,10 +58,10 @@ impl RealmFSUpdate {
|
|||||||
bail!("Cannot seal realmfs image, no sealing keys available");
|
bail!("Cannot seal realmfs image, no sealing keys available");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(RealmFSUpdate::new(realmfs.clone(), lock))
|
Ok(Update::new(realmfs, lock))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,11 +74,10 @@ impl RealmFSUpdate {
|
|||||||
info!("Update file {} already exists, removing it", self.target.display());
|
info!("Update file {} already exists, removing it", self.target.display());
|
||||||
util::remove_file(&self.target)?;
|
util::remove_file(&self.target)?;
|
||||||
}
|
}
|
||||||
info!("Creating update copy of realmfs {} -> {}",
|
|
||||||
self.realmfs.path().display(),
|
|
||||||
self.target().display());
|
|
||||||
self.realmfs.copy_image_file(self.target())?;
|
self.realmfs.copy_image_file(self.target())?;
|
||||||
|
|
||||||
|
self.truncate_verity()?;
|
||||||
|
self.resize_image_file()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,10 +104,6 @@ impl RealmFSUpdate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn mount_update_image(&mut self) -> Result<()> {
|
fn mount_update_image(&mut self) -> Result<()> {
|
||||||
info!("Loop device mounting {} at {}",
|
|
||||||
self.target().display(),
|
|
||||||
self.mountpath.display());
|
|
||||||
|
|
||||||
LoopDevice::with_loop(self.target(), Some(BLOCK_SIZE), false, |loopdev| {
|
LoopDevice::with_loop(self.target(), Some(BLOCK_SIZE), false, |loopdev| {
|
||||||
if self.resize.is_some() {
|
if self.resize.is_some() {
|
||||||
self.resize_device(loopdev)?;
|
self.resize_device(loopdev)?;
|
||||||
@@ -127,8 +115,9 @@ impl RealmFSUpdate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return size of image file in blocks based on metainfo `nblocks` field.
|
// Return size of image file in blocks based on metainfo `nblocks` field.
|
||||||
|
// Include header block in count so add one block
|
||||||
fn metainfo_nblock_size(&self) -> usize {
|
fn metainfo_nblock_size(&self) -> usize {
|
||||||
self.realmfs.metainfo().nblocks()
|
self.realmfs.metainfo().nblocks() + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unmount_update_image(&mut self) {
|
fn unmount_update_image(&mut self) {
|
||||||
@@ -170,8 +159,7 @@ impl RealmFSUpdate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn set_target_len(&self, nblocks: usize) -> Result<()> {
|
fn set_target_len(&self, nblocks: usize) -> Result<()> {
|
||||||
// add one block for header block
|
let len = (nblocks * BLOCK_SIZE) as u64;
|
||||||
let len = ((nblocks + 1) * BLOCK_SIZE) as u64;
|
|
||||||
let f = fs::OpenOptions::new()
|
let f = fs::OpenOptions::new()
|
||||||
.write(true)
|
.write(true)
|
||||||
.open(&self.target)
|
.open(&self.target)
|
||||||
@@ -183,13 +171,12 @@ impl RealmFSUpdate {
|
|||||||
|
|
||||||
// Remove dm-verity hash tree from update copy of image file.
|
// Remove dm-verity hash tree from update copy of image file.
|
||||||
fn truncate_verity(&self) -> Result<()> {
|
fn truncate_verity(&self) -> Result<()> {
|
||||||
info!("Truncating dm-verity hash tree from {}", self.target().display());
|
|
||||||
let file_nblocks = self.realmfs.file_nblocks()?;
|
let file_nblocks = self.realmfs.file_nblocks()?;
|
||||||
let metainfo_nblocks = self.metainfo_nblock_size();
|
let metainfo_nblocks = self.metainfo_nblock_size();
|
||||||
|
|
||||||
if self.realmfs.header().has_flag(ImageHeader::FLAG_HASH_TREE) {
|
if self.realmfs.header().has_flag(ImageHeader::FLAG_HASH_TREE) {
|
||||||
self.set_target_len(metainfo_nblocks)?;
|
self.set_target_len(metainfo_nblocks)?;
|
||||||
} else if file_nblocks > (metainfo_nblocks + 1) {
|
} else if file_nblocks > metainfo_nblocks {
|
||||||
warn!("RealmFS image size was greater than length indicated by metainfo.nblocks but FLAG_HASH_TREE not set");
|
warn!("RealmFS image size was greater than length indicated by metainfo.nblocks but FLAG_HASH_TREE not set");
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -198,12 +185,10 @@ impl RealmFSUpdate {
|
|||||||
// If resize was requested, adjust size of update copy of image file.
|
// If resize was requested, adjust size of update copy of image file.
|
||||||
fn resize_image_file(&self) -> Result<()> {
|
fn resize_image_file(&self) -> Result<()> {
|
||||||
let nblocks = match self.resize {
|
let nblocks = match self.resize {
|
||||||
Some(rs) => rs.nblocks(),
|
Some(rs) => rs.nblocks() + 1,
|
||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
};
|
};
|
||||||
|
|
||||||
info!("Resizing target file to {} blocks", nblocks);
|
|
||||||
|
|
||||||
if nblocks < self.metainfo_nblock_size() {
|
if nblocks < self.metainfo_nblock_size() {
|
||||||
bail!("Cannot shrink image")
|
bail!("Cannot shrink image")
|
||||||
}
|
}
|
||||||
@@ -214,19 +199,7 @@ impl RealmFSUpdate {
|
|||||||
self.set_target_len(nblocks)
|
self.set_target_len(nblocks)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shutdown_container(&mut self) -> Result<()> {
|
|
||||||
if let Some(update) = self.container.take() {
|
|
||||||
update.stop_container()?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cleanup(&mut self) {
|
pub fn cleanup(&mut self) {
|
||||||
// if a container was started, stop it
|
|
||||||
if let Err(err) = self.shutdown_container() {
|
|
||||||
warn!("Error shutting down update container: {}", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.mountpath.exists() {
|
if self.mountpath.exists() {
|
||||||
self.unmount_update_image();
|
self.unmount_update_image();
|
||||||
}
|
}
|
||||||
@@ -251,7 +224,7 @@ impl RealmFSUpdate {
|
|||||||
fn seal(&mut self) -> Result<()> {
|
fn seal(&mut self) -> Result<()> {
|
||||||
let nblocks = match self.resize {
|
let nblocks = match self.resize {
|
||||||
Some(rs) => rs.nblocks(),
|
Some(rs) => rs.nblocks(),
|
||||||
None => self.metainfo_nblock_size(),
|
None => self.metainfo_nblock_size() - 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let salt = hex::encode(randombytes(32));
|
let salt = hex::encode(randombytes(32));
|
||||||
@@ -259,11 +232,20 @@ impl RealmFSUpdate {
|
|||||||
.map_err(context!("failed to create verity context for realmfs update image {:?}", self.target()))?;
|
.map_err(context!("failed to create verity context for realmfs update image {:?}", self.target()))?;
|
||||||
let output = verity.generate_image_hashtree_with_salt(&salt, nblocks)
|
let output = verity.generate_image_hashtree_with_salt(&salt, nblocks)
|
||||||
.map_err(context!("failed to generate dm-verity hashtree for realmfs update image {:?}", self.target()))?;
|
.map_err(context!("failed to generate dm-verity hashtree for realmfs update image {:?}", self.target()))?;
|
||||||
|
// XXX passes metainfo for nblocks
|
||||||
|
//let output = Verity::new(&self.target).generate_image_hashtree_with_salt(&self.realmfs.metainfo(), &salt)?;
|
||||||
let root_hash = output.root_hash()
|
let root_hash = output.root_hash()
|
||||||
.ok_or_else(|| format_err!("no root hash returned from verity format operation"))?;
|
.ok_or_else(|| format_err!("no root hash returned from verity format operation"))?;
|
||||||
info!("root hash is {}", output.root_hash().unwrap());
|
info!("root hash is {}", output.root_hash().unwrap());
|
||||||
|
|
||||||
|
/*
|
||||||
|
let nblocks = match self.resize {
|
||||||
|
Some(rs) => rs.nblocks(),
|
||||||
|
None => self.metainfo_nblock_size() - 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
info!("Signing new image with user realmfs keys");
|
info!("Signing new image with user realmfs keys");
|
||||||
let metainfo_bytes = RealmFS::generate_metainfo(self.realmfs.name(), nblocks, salt.as_str(), root_hash);
|
let metainfo_bytes = RealmFS::generate_metainfo(self.realmfs.name(), nblocks, salt.as_str(), root_hash);
|
||||||
let keys = self.realmfs.sealing_keys().expect("No sealing keys");
|
let keys = self.realmfs.sealing_keys().expect("No sealing keys");
|
||||||
@@ -294,21 +276,6 @@ impl RealmFSUpdate {
|
|||||||
Ok(yes)
|
Ok(yes)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prepare_update(&mut self, shared_directory: bool) -> Result<()> {
|
|
||||||
if !is_euid_root() {
|
|
||||||
bail!("RealmFS updates must be prepared as root");
|
|
||||||
}
|
|
||||||
self.setup()?;
|
|
||||||
self.launch_update_container(shared_directory)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn commit_update(&mut self) -> Result<()> {
|
|
||||||
let result = self.apply_update();
|
|
||||||
self.cleanup();
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_interactive_update(&mut self, scheme: Option<&str>) -> Result<()> {
|
pub fn run_interactive_update(&mut self, scheme: Option<&str>) -> Result<()> {
|
||||||
if !is_euid_root() {
|
if !is_euid_root() {
|
||||||
bail!("RealmFS updates must be run as root");
|
bail!("RealmFS updates must be run as root");
|
||||||
@@ -340,21 +307,6 @@ impl RealmFSUpdate {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn launch_update_container(&mut self, shared_directory: bool) -> Result<()> {
|
|
||||||
|
|
||||||
if self.container.is_some() {
|
|
||||||
bail!("Update container is already running");
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("Launching update container '{}'", self.name());
|
|
||||||
|
|
||||||
let update = RealmFSUpdateLauncher::launch_update_container(self.name(), &self.mountpath, shared_directory)?;
|
|
||||||
|
|
||||||
self.container = Some(update);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_update_shell(&mut self, command: &str) -> Result<()> {
|
pub fn run_update_shell(&mut self, command: &str) -> Result<()> {
|
||||||
|
|
||||||
let mut alloc = BridgeAllocator::default_bridge()?;
|
let mut alloc = BridgeAllocator::default_bridge()?;
|
||||||
@@ -404,7 +356,7 @@ impl RealmFSUpdate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for RealmFSUpdate {
|
impl <'a> Drop for Update<'a> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.cleanup();
|
self.cleanup();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,12 +199,16 @@ impl ResourceImage {
|
|||||||
|
|
||||||
pub fn setup_verity_device(&self) -> Result<String> {
|
pub fn setup_verity_device(&self) -> Result<String> {
|
||||||
if !CommandLine::nosignatures() {
|
if !CommandLine::nosignatures() {
|
||||||
let pubkey = self.header.public_key()?;
|
match self.header.public_key()? {
|
||||||
|
Some(pubkey) => {
|
||||||
if !self.header.verify_signature(pubkey) {
|
if !self.header.verify_signature(pubkey) {
|
||||||
bail!("header signature verification failed");
|
bail!("header signature verification failed");
|
||||||
}
|
}
|
||||||
info!("Image header signature is valid");
|
info!("Image header signature is valid");
|
||||||
}
|
}
|
||||||
|
None => bail!("cannot verify header signature because no public key for channel {} is available", self.metainfo().channel())
|
||||||
|
}
|
||||||
|
}
|
||||||
info!("Setting up dm-verity device for image");
|
info!("Setting up dm-verity device for image");
|
||||||
if !self.has_verity_hashtree() {
|
if !self.has_verity_hashtree() {
|
||||||
self.generate_verity_hashtree()?;
|
self.generate_verity_hashtree()?;
|
||||||
@@ -368,11 +372,8 @@ impl ResourceImage {
|
|||||||
|
|
||||||
fn rootfs_channel() -> &'static str {
|
fn rootfs_channel() -> &'static str {
|
||||||
match CommandLine::channel_name() {
|
match CommandLine::channel_name() {
|
||||||
Some(channel) => channel,
|
|
||||||
None => match OsRelease::citadel_channel() {
|
|
||||||
Some(channel) => channel,
|
Some(channel) => channel,
|
||||||
None => "dev",
|
None => "dev",
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -419,10 +420,8 @@ fn compare_images(a: Option<ResourceImage>, b: ResourceImage) -> Result<Resource
|
|||||||
None => return Ok(b),
|
None => return Ok(b),
|
||||||
};
|
};
|
||||||
|
|
||||||
let bind_a = a.metainfo();
|
let ver_a = a.metainfo().version();
|
||||||
let bind_b = b.metainfo();
|
let ver_b = b.metainfo().version();
|
||||||
let ver_a = bind_a.version();
|
|
||||||
let ver_b = bind_b.version();
|
|
||||||
|
|
||||||
if ver_a > ver_b {
|
if ver_a > ver_b {
|
||||||
Ok(a)
|
Ok(a)
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ mod uname;
|
|||||||
|
|
||||||
pub use self::uname::UtsName;
|
pub use self::uname::UtsName;
|
||||||
pub use self::loopdev::LoopDevice;
|
pub use self::loopdev::LoopDevice;
|
||||||
pub use self::mounts::Mounts;
|
pub use self::mounts::{Mounts,MountLine};
|
||||||
pub use self::lock::FileLock;
|
pub use self::lock::FileLock;
|
||||||
|
|||||||
@@ -1,183 +0,0 @@
|
|||||||
use anyhow::Context;
|
|
||||||
use std::fmt;
|
|
||||||
use std::io::Write;
|
|
||||||
use std::slice::Iter;
|
|
||||||
|
|
||||||
pub const UPDATE_SERVER_HOSTNAME: &str = "update.subgraph.com";
|
|
||||||
const CITADEL_CONFIG_PATH: &str = "/storage/citadel-state/citadel.conf";
|
|
||||||
|
|
||||||
/// This struct embeds the CitadelVersion datastruct as well as the cryptographic validation of the that information
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct CryptoContainerFile {
|
|
||||||
pub serialized_citadel_version: Vec<u8>, // we serialize CitadelVersion
|
|
||||||
pub signature: String, // serialized CitadelVersion gets signed
|
|
||||||
pub signatory: String, // name of org or person who holds the key
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This struct contains the entirety of the logical information needed to decide whether to update or not
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub struct CitadelVersionStruct {
|
|
||||||
pub client: String,
|
|
||||||
pub channel: String, // dev, stable ...
|
|
||||||
pub component_version: Vec<AvailableComponentVersion>,
|
|
||||||
pub publisher: String, // name of org or person who released this update
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for CitadelVersionStruct {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"{} image with channel {} has components:\n",
|
|
||||||
self.client, self.channel
|
|
||||||
)?;
|
|
||||||
for i in &self.component_version {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"\n{} with version {} at location {}",
|
|
||||||
i.component, i.version, i.file_path
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Clone, Deserialize, PartialEq, Eq, Ord)]
|
|
||||||
pub struct AvailableComponentVersion {
|
|
||||||
pub component: Component, // rootfs, kernel or extra
|
|
||||||
pub version: String, // stored as semver
|
|
||||||
pub file_path: String,
|
|
||||||
pub sha256_hash: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for AvailableComponentVersion {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
||||||
// absolutely require that the components be in the same order in all structs (rootfs, kernel, extra)
|
|
||||||
if &self.component != &other.component {
|
|
||||||
panic!("ComponentVersion comparison failed because comparing different components");
|
|
||||||
}
|
|
||||||
Some(
|
|
||||||
semver::Version::parse(&self.version)
|
|
||||||
.unwrap()
|
|
||||||
.cmp(&semver::Version::parse(&other.version).unwrap()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for AvailableComponentVersion {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"({} image has version: {})",
|
|
||||||
self.component, self.version
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, clap::ValueEnum)]
|
|
||||||
pub enum Component {
|
|
||||||
Rootfs,
|
|
||||||
Kernel,
|
|
||||||
Extra,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component {
|
|
||||||
pub fn iterator() -> Iter<'static, Component> {
|
|
||||||
static COMPONENTS: [Component; 3] =
|
|
||||||
[Component::Rootfs, Component::Kernel, Component::Extra];
|
|
||||||
COMPONENTS.iter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Component {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Component::Rootfs => write!(f, "rootfs"),
|
|
||||||
Component::Kernel => write!(f, "kernel"),
|
|
||||||
&Component::Extra => write!(f, "extra"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reads a specific key from an os-release formatted file.
|
|
||||||
/// The value is returned without any surrounding quotes.
|
|
||||||
pub fn get_citadel_conf(key: &str) -> anyhow::Result<Option<String>> {
|
|
||||||
// Read the entire file into a string.
|
|
||||||
let content = std::fs::read_to_string(CITADEL_CONFIG_PATH)
|
|
||||||
.context(format!("Failed to read {}", CITADEL_CONFIG_PATH))?;
|
|
||||||
|
|
||||||
// Search each line for the key.
|
|
||||||
for line in content.lines() {
|
|
||||||
// Check if the line starts with "KEY="
|
|
||||||
if let Some(value) = line.trim().strip_prefix(&format!("{}=", key)) {
|
|
||||||
// If found, trim whitespace and quotes from the value and return.
|
|
||||||
let value = value.trim().trim_matches('"').to_string();
|
|
||||||
return Ok(Some(value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the loop finishes without finding the key, return None.
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_os_release(key: &str) -> anyhow::Result<Option<String>> {
|
|
||||||
// Read the entire file into a string.
|
|
||||||
let content = std::fs::read_to_string("/etc/os-release")
|
|
||||||
.context(format!("Failed to read {}", "/etc/os-release"))?;
|
|
||||||
|
|
||||||
// Search each line for the key.
|
|
||||||
for line in content.lines() {
|
|
||||||
// Check if the line starts with "KEY="
|
|
||||||
if let Some(value) = line.trim().strip_prefix(&format!("{}=", key)) {
|
|
||||||
// If found, trim whitespace and quotes from the value and return.
|
|
||||||
let value = value.trim().trim_matches('"').to_string();
|
|
||||||
return Ok(Some(value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the loop finishes without finding the key, return None.
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Safely modifies a key-value pair in the citadel config os-release-formated file.
|
|
||||||
/// If the key does not exist, it will be added to the end of the file.
|
|
||||||
pub fn set_citadel_conf(key: &str, value: &str) -> anyhow::Result<()> {
|
|
||||||
// Read the existing os-release file.
|
|
||||||
let content = std::fs::read_to_string(CITADEL_CONFIG_PATH)
|
|
||||||
.context(format!("Failed to read {}", CITADEL_CONFIG_PATH))?;
|
|
||||||
|
|
||||||
let mut lines: Vec<String> = Vec::new();
|
|
||||||
let mut key_updated = false;
|
|
||||||
let key_prefix = format!("{}=", key);
|
|
||||||
let new_line = format!("{}{}", key_prefix, value);
|
|
||||||
|
|
||||||
// Process each line to update the key if it exists.
|
|
||||||
for line in content.lines() {
|
|
||||||
if line.starts_with(&key_prefix) {
|
|
||||||
lines.push(new_line.clone());
|
|
||||||
key_updated = true;
|
|
||||||
} else {
|
|
||||||
lines.push(line.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the key was not found, add it to the end.
|
|
||||||
if !key_updated {
|
|
||||||
lines.push(new_line);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the changes back safely using a temporary file and an atomic rename.
|
|
||||||
let mut temp_file = tempfile::Builder::new()
|
|
||||||
.prefix("citadel.conf")
|
|
||||||
.suffix(".tmp")
|
|
||||||
.tempfile_in(std::path::Path::new(CITADEL_CONFIG_PATH).parent().unwrap())?;
|
|
||||||
|
|
||||||
temp_file.write_all(lines.join("\n").as_bytes())?;
|
|
||||||
temp_file.write_all(b"\n")?; // Ensure the file ends with a newline.
|
|
||||||
|
|
||||||
temp_file.persist(CITADEL_CONFIG_PATH).context(format!(
|
|
||||||
"Failed to overwrite {}. Are you running as root?",
|
|
||||||
CITADEL_CONFIG_PATH
|
|
||||||
))?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,6 @@ use std::env;
|
|||||||
use std::fs::{self, File, DirEntry};
|
use std::fs::{self, File, DirEntry};
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use std::io::{self, Seek, Read, BufReader, SeekFrom};
|
use std::io::{self, Seek, Read, BufReader, SeekFrom};
|
||||||
use std::os::fd::AsRawFd;
|
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
@@ -48,12 +47,6 @@ fn search_path(filename: &str) -> Result<PathBuf> {
|
|||||||
bail!("could not find {} in $PATH", filename)
|
bail!("could not find {} in $PATH", filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn append_to_path(p: &Path, s: &str) -> PathBuf {
|
|
||||||
let mut p_osstr = p.as_os_str().to_owned();
|
|
||||||
p_osstr.push(s);
|
|
||||||
p_osstr.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ensure_command_exists(cmd: &str) -> Result<()> {
|
pub fn ensure_command_exists(cmd: &str) -> Result<()> {
|
||||||
let path = Path::new(cmd);
|
let path = Path::new(cmd);
|
||||||
if !path.is_absolute() {
|
if !path.is_absolute() {
|
||||||
@@ -224,8 +217,7 @@ where
|
|||||||
///
|
///
|
||||||
pub fn remove_file(path: impl AsRef<Path>) -> Result<()> {
|
pub fn remove_file(path: impl AsRef<Path>) -> Result<()> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let is_symlink = fs::symlink_metadata(path).is_ok();
|
if path.exists() {
|
||||||
if is_symlink || path.exists() {
|
|
||||||
fs::remove_file(path)
|
fs::remove_file(path)
|
||||||
.map_err(context!("failed to remove file {:?}", path))?;
|
.map_err(context!("failed to remove file {:?}", path))?;
|
||||||
}
|
}
|
||||||
@@ -376,38 +368,9 @@ pub fn touch_mtime(path: &Path) -> Result<()> {
|
|||||||
|
|
||||||
utimes(path, meta.atime(),mtime)?;
|
utimes(path, meta.atime(),mtime)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
|
||||||
|
|
||||||
pub fn nsenter_netns(netns: &str) -> Result<()> {
|
|
||||||
let mut path = PathBuf::from("/run/netns");
|
|
||||||
path.push(netns);
|
|
||||||
if !path.exists() {
|
|
||||||
bail!("Network namespace '{}' does not exist", netns);
|
|
||||||
}
|
|
||||||
let f = File::open(&path)
|
|
||||||
.map_err(context!("error opening netns file {}", path.display()))?;
|
|
||||||
|
|
||||||
let fd = f.as_raw_fd();
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
if libc::setns(fd, libc::CLONE_NEWNET) == -1 {
|
|
||||||
let err = io::Error::last_os_error();
|
|
||||||
bail!("failed to setns() into network namespace '{}': {}", netns, err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn drop_privileges(uid: u32, gid: u32) -> Result<()> {
|
|
||||||
unsafe {
|
|
||||||
if libc::setgid(gid) == -1 {
|
|
||||||
let err = io::Error::last_os_error();
|
|
||||||
bail!("failed to call setgid({}): {}", gid, err);
|
|
||||||
|
|
||||||
} else if libc::setuid(uid) == -1 {
|
|
||||||
let err = io::Error::last_os_error();
|
|
||||||
bail!("failed to call setuid({}): {}", uid, err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
15
realm-config-ui/Cargo.toml
Normal file
15
realm-config-ui/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "realm-config-ui"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Bruce Leidl <bruce@subgraph.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
description = "Realm Configuration Tool"
|
||||||
|
homepage = "https://subgraph.com"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
libcitadel = { path = "../libcitadel" }
|
||||||
|
rand = "0.8"
|
||||||
|
zvariant = "2.7.0"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
zbus = "=2.0.0-beta.5"
|
||||||
|
gtk = { version = "0.14.0", features = ["v3_24"] }
|
||||||
77
realm-config-ui/src/colorscheme/colorscheme-dialog.ui
Normal file
77
realm-config-ui/src/colorscheme/colorscheme-dialog.ui
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
<template class="ColorSchemeDialog" parent="GtkDialog">
|
||||||
|
<property name="title">Choose Terminal Colors</property>
|
||||||
|
<property name="modal">True</property>
|
||||||
|
|
||||||
|
<child internal-child="vbox">
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="orientation">horizontal</property>
|
||||||
|
|
||||||
|
<child>
|
||||||
|
<object class="GtkScrolledWindow">
|
||||||
|
<property name="hscrollbar-policy">never</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkTreeView" id="colorscheme-tree">
|
||||||
|
<property name="headers-visible">False</property>
|
||||||
|
<property name="model">treemodel</property>
|
||||||
|
|
||||||
|
<child>
|
||||||
|
<object class="GtkTreeViewColumn">
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkCellRendererText"/>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="text">1</attribute>
|
||||||
|
</attributes>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<child>
|
||||||
|
<object class="GtkSeparator">
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="colorscheme-label">
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="halign">fill</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<child type="action">
|
||||||
|
<object class="GtkButton" id="cancel_button">
|
||||||
|
<property name="use-underline">1</property>
|
||||||
|
<property name="label">Cancel</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child type="action">
|
||||||
|
<object class="GtkButton" id="ok_button">
|
||||||
|
<property name="use-underline">1</property>
|
||||||
|
<property name="label">_Choose</property>
|
||||||
|
<property name="can-default">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<action-widgets>
|
||||||
|
<action-widget response="cancel">cancel_button</action-widget>
|
||||||
|
<action-widget response="ok" default="true">ok_button</action-widget>
|
||||||
|
</action-widgets>
|
||||||
|
</template>
|
||||||
|
<object class="GtkTreeStore" id="treemodel">
|
||||||
|
<columns>
|
||||||
|
<column type="gchararray" />
|
||||||
|
<column type="gchararray" />
|
||||||
|
</columns>
|
||||||
|
</object>
|
||||||
|
</interface>
|
||||||
216
realm-config-ui/src/colorscheme/colorschemes.rs
Normal file
216
realm-config-ui/src/colorscheme/colorschemes.rs
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use gtk::glib;
|
||||||
|
use libcitadel::terminal::{Base16Scheme, Color};
|
||||||
|
|
||||||
|
enum RootEntry {
|
||||||
|
Scheme(Base16Scheme),
|
||||||
|
Category(String, Vec<Base16Scheme>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RootEntry {
|
||||||
|
fn key(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
RootEntry::Scheme(scheme) => scheme.slug(),
|
||||||
|
RootEntry::Category(name, _) => name.as_str(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_to_category(list: &mut Vec<RootEntry>, category: &str, scheme: &Base16Scheme) {
|
||||||
|
let scheme = scheme.clone();
|
||||||
|
for entry in list.iter_mut() {
|
||||||
|
if let RootEntry::Category(name, schemes) = entry {
|
||||||
|
if name == category {
|
||||||
|
schemes.push(scheme);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list.push(RootEntry::Category(category.to_string(), vec![scheme]))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_list() -> Vec<RootEntry> {
|
||||||
|
let mut list = Vec::new();
|
||||||
|
for scheme in Base16Scheme::all_schemes() {
|
||||||
|
if let Some(category) = scheme.category() {
|
||||||
|
Self::add_to_category(&mut list,category, &scheme);
|
||||||
|
} else {
|
||||||
|
list.push(RootEntry::Scheme(scheme));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list.sort_by(|a, b| a.key().cmp(b.key()));
|
||||||
|
list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ColorSchemes {
|
||||||
|
entries: Rc<Vec<RootEntry>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorSchemes {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
ColorSchemes {
|
||||||
|
entries: Rc::new(RootEntry::build_list()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn populate_tree_model(&self, store: >k::TreeStore) {
|
||||||
|
for entry in self.entries.iter() {
|
||||||
|
match entry {
|
||||||
|
RootEntry::Scheme(scheme) => {
|
||||||
|
let first = scheme.slug().to_string();
|
||||||
|
let second = scheme.name().to_string();
|
||||||
|
store.insert_with_values(None, None, &[(0, &first), (1, &second)]);
|
||||||
|
}
|
||||||
|
RootEntry::Category(name, list) => {
|
||||||
|
let first = String::new();
|
||||||
|
let second = name.to_string();
|
||||||
|
let iter = store.insert_with_values(None, None, &[(0, &first), (1, &second)]);
|
||||||
|
for scheme in list {
|
||||||
|
let first = scheme.slug().to_string();
|
||||||
|
let second = scheme.name().to_string();
|
||||||
|
store.insert_with_values(Some(&iter), None, &[(0, &first), (1, &second)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn preview_scheme(&self, id: &str) -> Option<(String, Color)> {
|
||||||
|
let scheme = Base16Scheme::by_name(id)?;
|
||||||
|
let bg = scheme.terminal_background();
|
||||||
|
let text = PreviewRender::new(scheme).render_preview();
|
||||||
|
Some((text, bg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PreviewRender {
|
||||||
|
buffer: String,
|
||||||
|
scheme: Base16Scheme,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PreviewRender {
|
||||||
|
fn new(scheme: &Base16Scheme) -> Self {
|
||||||
|
let scheme = scheme.clone();
|
||||||
|
PreviewRender {
|
||||||
|
buffer: String::new(),
|
||||||
|
scheme,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn print(mut self, color_idx: usize, text: &str) -> Self {
|
||||||
|
let s = glib::markup_escape_text(text);
|
||||||
|
|
||||||
|
let color = self.scheme.terminal_palette_color(color_idx);
|
||||||
|
self.color_span(Some(color), None);
|
||||||
|
self.buffer.push_str(s.as_str());
|
||||||
|
self.end_span();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vtype(self, text: &str) -> Self {
|
||||||
|
self.print(3, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn konst(self, text: &str) -> Self {
|
||||||
|
self.print(1, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn func(self, text: &str) -> Self {
|
||||||
|
self.print(4, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string(self, text: &str) -> Self {
|
||||||
|
self.print(2, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn keyword(self, text: &str) -> Self {
|
||||||
|
self.print(5, text)
|
||||||
|
}
|
||||||
|
fn comment(self, text: &str) -> Self {
|
||||||
|
self.print(8, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text(mut self, text: &str) -> Self {
|
||||||
|
let color = self.scheme.terminal_foreground();
|
||||||
|
self.color_span(Some(color), None);
|
||||||
|
self.buffer.push_str(text);
|
||||||
|
self.end_span();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn color_attrib(&mut self, name: &str, color: Color) {
|
||||||
|
let (r,g,b) = color.rgb();
|
||||||
|
self.buffer.push_str(&format!(" {}='#{:02X}{:02X}{:02X}'", name, r, g, b));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color_span(&mut self, fg: Option<Color>, bg: Option<Color>) {
|
||||||
|
self.buffer.push_str("<span");
|
||||||
|
if let Some(color) = fg {
|
||||||
|
self.color_attrib("foreground", color);
|
||||||
|
}
|
||||||
|
if let Some(color) = bg {
|
||||||
|
self.color_attrib("background", color);
|
||||||
|
}
|
||||||
|
self.buffer.push_str(">");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end_span(&mut self) {
|
||||||
|
self.buffer.push_str("</span>");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nl(mut self) -> Self {
|
||||||
|
self.buffer.push_str(" \n ");
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_colorbar(&mut self) {
|
||||||
|
self.buffer.push_str("\n ");
|
||||||
|
let color = self.scheme.terminal_foreground();
|
||||||
|
self.color_span(Some(color), None);
|
||||||
|
for i in 0..16 {
|
||||||
|
self.buffer.push_str(&format!(" {:X} ", i));
|
||||||
|
}
|
||||||
|
self.end_span();
|
||||||
|
self.buffer.push_str(" \n ");
|
||||||
|
for i in 0..16 {
|
||||||
|
let c = self.scheme.color(i);
|
||||||
|
self.color_span(None, Some(c));
|
||||||
|
self.buffer.push_str(" ");
|
||||||
|
self.end_span();
|
||||||
|
}
|
||||||
|
self.buffer.push_str(" \n ");
|
||||||
|
for i in 8..16 {
|
||||||
|
let c = self.scheme.terminal_palette_color(i);
|
||||||
|
self.color_span(None, Some(c));
|
||||||
|
self.buffer.push_str(" ");
|
||||||
|
self.end_span();
|
||||||
|
}
|
||||||
|
self.buffer.push_str(" \n ");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_preview(mut self) -> String {
|
||||||
|
let name = self.scheme.name().to_string();
|
||||||
|
self.render_colorbar();
|
||||||
|
self.nl()
|
||||||
|
.comment("/**").nl()
|
||||||
|
.comment(" * An example of how this color scheme").nl()
|
||||||
|
.comment(" * might look in a text editor with syntax").nl()
|
||||||
|
.comment(" * highlighting.").nl()
|
||||||
|
.comment(" */").nl()
|
||||||
|
.nl()
|
||||||
|
.func("#include ").string("<stdio.h>").nl()
|
||||||
|
.func("#include ").string("<stdlib.h>").nl()
|
||||||
|
.nl()
|
||||||
|
.vtype("static char").text(" theme[] = ").string(&format!("\"{}\"", name)).text(";").nl()
|
||||||
|
.nl()
|
||||||
|
.vtype("int").text(" main(").vtype("int").text(" argc, ").vtype("char").text(" **argv) {").nl()
|
||||||
|
.text(" printf(").string("\"Hello, ").keyword("%s").text("!").keyword("\\n").string("\"").text(", theme);").nl()
|
||||||
|
.text(" exit(").konst("0").text(");").nl()
|
||||||
|
.text("}")
|
||||||
|
.nl()
|
||||||
|
.nl().buffer
|
||||||
|
}
|
||||||
|
}
|
||||||
153
realm-config-ui/src/colorscheme/dialog.rs
Normal file
153
realm-config-ui/src/colorscheme/dialog.rs
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
use gtk::glib;
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use gtk::subclass::prelude::*;
|
||||||
|
use gtk::CompositeTemplate;
|
||||||
|
|
||||||
|
use libcitadel::terminal::{Base16Scheme, Color};
|
||||||
|
|
||||||
|
use crate::colorscheme::colorschemes::ColorSchemes;
|
||||||
|
|
||||||
|
#[derive(CompositeTemplate)]
|
||||||
|
#[template(file = "colorscheme-dialog.ui")]
|
||||||
|
pub struct ColorSchemeDialog {
|
||||||
|
#[template_child(id="colorscheme-tree")]
|
||||||
|
tree: TemplateChild<gtk::TreeView>,
|
||||||
|
|
||||||
|
#[template_child]
|
||||||
|
treemodel: TemplateChild<gtk::TreeStore>,
|
||||||
|
|
||||||
|
#[template_child(id="colorscheme-label")]
|
||||||
|
preview: TemplateChild<gtk::Label>,
|
||||||
|
|
||||||
|
css_provider: gtk::CssProvider,
|
||||||
|
|
||||||
|
colorschemes: ColorSchemes,
|
||||||
|
|
||||||
|
tracker: RefCell<Option<SelectionTracker>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct SelectionTracker {
|
||||||
|
model: gtk::TreeStore,
|
||||||
|
selection: gtk::TreeSelection,
|
||||||
|
preview: gtk::Label,
|
||||||
|
colorschemes: ColorSchemes,
|
||||||
|
css_provider: gtk::CssProvider,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SelectionTracker {
|
||||||
|
fn new(dialog: &ColorSchemeDialog) -> Self {
|
||||||
|
let tracker = SelectionTracker {
|
||||||
|
model: dialog.treemodel.clone(),
|
||||||
|
selection: dialog.tree.selection(),
|
||||||
|
preview: dialog.preview.clone(),
|
||||||
|
colorschemes: dialog.colorschemes.clone(),
|
||||||
|
css_provider: dialog.css_provider.clone(),
|
||||||
|
};
|
||||||
|
tracker.selection.connect_changed(glib::clone!(@strong tracker => move |_| {
|
||||||
|
if let Some(id) = tracker.selected_id() {
|
||||||
|
if let Some((text, background)) = tracker.colorschemes.preview_scheme(&id) {
|
||||||
|
tracker.set_preview_background(background);
|
||||||
|
tracker.preview.set_markup(&text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
tracker
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selected_id(&self) -> Option<String> {
|
||||||
|
self.selection.selected().and_then(|(model,iter)| {
|
||||||
|
model.value(&iter, 0).get::<String>().ok()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_preview_background(&self, color: Color) {
|
||||||
|
const CSS: &str =
|
||||||
|
r##"
|
||||||
|
#colorscheme-label {
|
||||||
|
background-color: $COLOR;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 14pt;
|
||||||
|
}
|
||||||
|
"##;
|
||||||
|
let (r, g, b) = color.rgb();
|
||||||
|
let css = CSS.replace("$COLOR", &format!("#{:02x}{:02x}{:02x}", r, g, b));
|
||||||
|
if let Err(e) = self.css_provider.load_from_data(css.as_bytes()) {
|
||||||
|
warn!("Error loading CSS provider data: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_selected_id(&self, id: &str) {
|
||||||
|
self.model.foreach(glib::clone!(@strong self.selection as selection => move |model, _path, iter| {
|
||||||
|
if let Ok(ref s) = model.value(iter, 0).get::<String>() {
|
||||||
|
if s == id {
|
||||||
|
selection.select_iter(iter);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorSchemeDialog {
|
||||||
|
pub fn set_selected_id(&self, colorscheme_id: &str) {
|
||||||
|
let tracker = self.tracker.borrow();
|
||||||
|
if let Some(tracker) = tracker.as_ref() {
|
||||||
|
tracker.set_selected_id(colorscheme_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_selected_scheme (&self) -> Option<Base16Scheme> {
|
||||||
|
let tracker = self.tracker.borrow();
|
||||||
|
tracker.as_ref().and_then(|t| t.selected_id())
|
||||||
|
.and_then(|id| Base16Scheme::by_name(&id))
|
||||||
|
.cloned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ColorSchemeDialog {
|
||||||
|
fn default() -> Self {
|
||||||
|
ColorSchemeDialog {
|
||||||
|
tree: Default::default(),
|
||||||
|
treemodel: Default::default(),
|
||||||
|
preview: Default::default(),
|
||||||
|
css_provider: gtk::CssProvider::new(),
|
||||||
|
colorschemes: ColorSchemes::new(),
|
||||||
|
tracker: RefCell::new(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for ColorSchemeDialog {
|
||||||
|
const NAME: &'static str = "ColorSchemeDialog";
|
||||||
|
type Type = super::ColorSchemeDialog;
|
||||||
|
type ParentType = gtk::Dialog;
|
||||||
|
|
||||||
|
fn class_init(klass: &mut Self::Class) {
|
||||||
|
Self::bind_template(klass);
|
||||||
|
}
|
||||||
|
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||||
|
obj.init_template();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectImpl for ColorSchemeDialog {
|
||||||
|
fn constructed(&self, obj: &Self::Type) {
|
||||||
|
self.parent_constructed(obj);
|
||||||
|
self.preview.set_widget_name("colorscheme-label");
|
||||||
|
self.preview.style_context().add_provider(&self.css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||||
|
self.colorschemes.populate_tree_model(&self.treemodel);
|
||||||
|
let tracker = SelectionTracker::new(self);
|
||||||
|
self.tracker.borrow_mut().replace(tracker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DialogImpl for ColorSchemeDialog {}
|
||||||
|
impl WindowImpl for ColorSchemeDialog {}
|
||||||
|
impl BinImpl for ColorSchemeDialog {}
|
||||||
|
impl ContainerImpl for ColorSchemeDialog {}
|
||||||
|
impl WidgetImpl for ColorSchemeDialog {}
|
||||||
31
realm-config-ui/src/colorscheme/mod.rs
Normal file
31
realm-config-ui/src/colorscheme/mod.rs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
use gtk::glib;
|
||||||
|
use glib::subclass::prelude::*;
|
||||||
|
use libcitadel::terminal::Base16Scheme;
|
||||||
|
|
||||||
|
mod dialog;
|
||||||
|
mod colorschemes;
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct ColorSchemeDialog(ObjectSubclass<dialog::ColorSchemeDialog>)
|
||||||
|
@extends gtk::Dialog, gtk::Window, gtk::Bin, gtk::Container, gtk::Widget,
|
||||||
|
@implements gtk::Buildable;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorSchemeDialog {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
glib::Object::new(&[("use-header-bar", &1)])
|
||||||
|
.expect("Failed to create ColorSchemeDialog")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instance(&self) -> &dialog::ColorSchemeDialog {
|
||||||
|
dialog::ColorSchemeDialog::from_instance(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_selected_scheme(&self) -> Option<Base16Scheme> {
|
||||||
|
self.instance().get_selected_scheme()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_selected_scheme(&self, id: &str) {
|
||||||
|
self.instance().set_selected_id(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
155
realm-config-ui/src/configure_dialog/configure-dialog.ui
Normal file
155
realm-config-ui/src/configure_dialog/configure-dialog.ui
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
|
||||||
|
<template class="ConfigureDialog" parent="GtkDialog">
|
||||||
|
<property name="title">Configure Realm</property>
|
||||||
|
<property name="modal">True</property>
|
||||||
|
<child internal-child="vbox">
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="margin">20</property>
|
||||||
|
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="label">Options</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<child>
|
||||||
|
<object class="GtkFrame">
|
||||||
|
<property name="margin-bottom">20</property>
|
||||||
|
<child>
|
||||||
|
<!-- -->
|
||||||
|
<object class="GtkListBox" id="bool-options-box">
|
||||||
|
<property name="margin">10</property>
|
||||||
|
<property name="selection_mode">none</property>
|
||||||
|
<property name="activate_on_single_click">False</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="tooltip-markup"><![CDATA[<b><big>Overlay</big></b>
|
||||||
|
|
||||||
|
Type of rootfs overlay realm is configured to use.
|
||||||
|
|
||||||
|
<b>None</b> Don't use a rootfs overlay
|
||||||
|
<b>TmpFS</b> Use a rootfs overlay stored on tmpfs
|
||||||
|
<b>Storage</b> Use a rootfs overlay stored on disk in storage partition
|
||||||
|
]]></property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="label">Overlay</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkComboBoxText" id="overlay-combo">
|
||||||
|
<property name="active">0</property>
|
||||||
|
<items>
|
||||||
|
<item id="storage">Storage</item>
|
||||||
|
<item id="tmpfs">TmpFS</item>
|
||||||
|
<item id="none">None</item>
|
||||||
|
</items>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="tooltip-markup"><![CDATA[<b><big>RealmFS</big></b>
|
||||||
|
|
||||||
|
Root filesystem image to use for realm.
|
||||||
|
]]></property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="label">RealmFS</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkComboBoxText" id="realmfs-combo">
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="tooltip-markup"><![CDATA[<b><big>Terminal Color Scheme</big></b>
|
||||||
|
|
||||||
|
Choose a color scheme to use in terminals in this realm.
|
||||||
|
]]></property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="label">Color Scheme</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="color-scheme-button">
|
||||||
|
<property name="label">Default Dark</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="tooltip-markup"><![CDATA[<b><big>Window Frame Color</big></b>
|
||||||
|
|
||||||
|
Set a color to be used when frames are drawn around application windows for this realm.
|
||||||
|
]]></property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="label">Frame Color</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkColorButton" id="frame-color-button">
|
||||||
|
<property name="color">#ffff00000000</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child type="action">
|
||||||
|
<object class="GtkButton" id="cancel_button">
|
||||||
|
<property name="use-underline">1</property>
|
||||||
|
<property name="label">Cancel</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child type="action">
|
||||||
|
<object class="GtkButton" id="ok_button">
|
||||||
|
<property name="use-underline">1</property>
|
||||||
|
<property name="label">Apply</property>
|
||||||
|
<property name="can-default">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<action-widgets>
|
||||||
|
<action-widget response="cancel">cancel_button</action-widget>
|
||||||
|
<action-widget response="ok" default="true">ok_button</action-widget>
|
||||||
|
</action-widgets>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
<object class="GtkSizeGroup">
|
||||||
|
<widgets>
|
||||||
|
<widget name="overlay-combo" />
|
||||||
|
<widget name="realmfs-combo" />
|
||||||
|
<widget name="color-scheme-button" />
|
||||||
|
<widget name="frame-color-button" />
|
||||||
|
</widgets>
|
||||||
|
</object>
|
||||||
|
</interface>
|
||||||
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
<template class="ConfigureOption" parent="GtkListBoxRow">
|
||||||
|
<property name="width_request">100</property>
|
||||||
|
<property name="activatable">False</property>
|
||||||
|
<property name="selectable">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="margin-bottom">5</property>
|
||||||
|
<property name="spacing">30</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="name">
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSwitch" id="switch">
|
||||||
|
<property name="halign">end</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</interface>
|
||||||
203
realm-config-ui/src/configure_dialog/dialog.rs
Normal file
203
realm-config-ui/src/configure_dialog/dialog.rs
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
use std::cell::{Ref, RefCell};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use gtk::glib;
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use gtk::subclass::prelude::*;
|
||||||
|
use gtk::CompositeTemplate;
|
||||||
|
|
||||||
|
use crate::colorscheme::ColorSchemeDialog;
|
||||||
|
use crate::configure_dialog::ConfigOptions;
|
||||||
|
use crate::configure_dialog::settings::CitadelSettings;
|
||||||
|
use crate::realmsd::RealmConfig;
|
||||||
|
|
||||||
|
#[derive(CompositeTemplate)]
|
||||||
|
#[template(file = "configure-dialog.ui")]
|
||||||
|
pub struct ConfigureDialog {
|
||||||
|
#[template_child(id="bool-options-box")]
|
||||||
|
bool_option_list: TemplateChild<gtk::ListBox>,
|
||||||
|
|
||||||
|
#[template_child(id="overlay-combo")]
|
||||||
|
overlay: TemplateChild<gtk::ComboBoxText>,
|
||||||
|
|
||||||
|
#[template_child(id="realmfs-combo")]
|
||||||
|
realmfs: TemplateChild<gtk::ComboBoxText>,
|
||||||
|
|
||||||
|
#[template_child(id="color-scheme-button")]
|
||||||
|
colorscheme: TemplateChild<gtk::Button>,
|
||||||
|
|
||||||
|
#[template_child(id="frame-color-button")]
|
||||||
|
frame_color: TemplateChild<gtk::ColorButton>,
|
||||||
|
|
||||||
|
options: Rc<RefCell<ConfigOptions>>,
|
||||||
|
|
||||||
|
bool_option_rows: RefCell<Vec<super::ConfigureOption>>,
|
||||||
|
|
||||||
|
colorscheme_dialog: ColorSchemeDialog,
|
||||||
|
|
||||||
|
settings: RefCell<CitadelSettings>,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigureDialog {
|
||||||
|
|
||||||
|
pub fn set_realm_name(&self, name: &str) {
|
||||||
|
let color = self.settings.borrow().get_realm_color(Some(name));
|
||||||
|
self.frame_color.set_rgba(&color);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset_options(&self) {
|
||||||
|
self.options.borrow_mut().reset();
|
||||||
|
self.update_options();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_config(&self, config: &RealmConfig) {
|
||||||
|
self.options.borrow_mut().configure(config);
|
||||||
|
self.realmfs.remove_all();
|
||||||
|
|
||||||
|
self.update_options();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn changes(&self) -> Vec<(String,String)> {
|
||||||
|
self.options.borrow().changes()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn store_settings(&self, realm_name: &str) {
|
||||||
|
let color = self.frame_color.rgba();
|
||||||
|
self.settings.borrow_mut().store_realm_color(realm_name, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn options(&self) -> Ref<ConfigOptions> {
|
||||||
|
self.options.borrow()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_realmfs(&self) {
|
||||||
|
self.realmfs.remove_all();
|
||||||
|
for realmfs in self.options().realmfs_list() {
|
||||||
|
self.realmfs.append(Some(realmfs.as_str()), realmfs.as_str());
|
||||||
|
}
|
||||||
|
let current = self.options().realmfs();
|
||||||
|
self.realmfs.set_active_id(Some(¤t));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_options(&self) {
|
||||||
|
let rows = self.bool_option_rows.borrow();
|
||||||
|
for row in rows.iter() {
|
||||||
|
row.update();
|
||||||
|
}
|
||||||
|
let overlay_id = self.options().overlay_id();
|
||||||
|
self.overlay.set_active_id(Some(&overlay_id));
|
||||||
|
|
||||||
|
self.update_realmfs();
|
||||||
|
|
||||||
|
let scheme = self.options().colorscheme();
|
||||||
|
self.colorscheme.set_label(scheme.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_option_rows(&self) {
|
||||||
|
let mut rows = self.bool_option_rows.borrow_mut();
|
||||||
|
let options = self.options.borrow();
|
||||||
|
for op in options.bool_options() {
|
||||||
|
let w = super::ConfigureOption::new(op);
|
||||||
|
self.bool_option_list.add(&w);
|
||||||
|
rows.push(w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_overlay(&self) {
|
||||||
|
let options = self.options.clone();
|
||||||
|
self.overlay.connect_changed(move |combo| {
|
||||||
|
if let Some(text) = combo.active_id() {
|
||||||
|
options.borrow_mut().set_overlay_id(text.as_str());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_realmfs(&self) {
|
||||||
|
let options = self.options.clone();
|
||||||
|
self.realmfs.connect_changed(move |combo| {
|
||||||
|
if let Some(text) = combo.active_text() {
|
||||||
|
options.borrow_mut().set_realmfs(text.as_str());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_colorscheme(&self) {
|
||||||
|
let dialog = self.colorscheme_dialog.clone();
|
||||||
|
let options = self.options.clone();
|
||||||
|
|
||||||
|
self.colorscheme.connect_clicked(move |b| {
|
||||||
|
dialog.show_all();
|
||||||
|
let scheme = options.borrow().colorscheme();
|
||||||
|
dialog.set_selected_scheme(scheme.slug());
|
||||||
|
|
||||||
|
match dialog.run() {
|
||||||
|
gtk::ResponseType::Ok => {
|
||||||
|
if let Some(scheme) = dialog.get_selected_scheme() {
|
||||||
|
options.borrow_mut().set_colorscheme_id(scheme.slug());
|
||||||
|
b.set_label(scheme.name());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
dialog.hide();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_frame_color(&self) {
|
||||||
|
let color = self.settings.borrow().get_realm_color(None);
|
||||||
|
self.frame_color.set_rgba(&color);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_widgets(&self) {
|
||||||
|
self.create_option_rows();
|
||||||
|
self.setup_overlay();
|
||||||
|
self.setup_realmfs();
|
||||||
|
self.setup_colorscheme();
|
||||||
|
self.setup_frame_color();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ConfigureDialog {
|
||||||
|
fn default() -> Self {
|
||||||
|
ConfigureDialog {
|
||||||
|
bool_option_list: Default::default(),
|
||||||
|
overlay: Default::default(),
|
||||||
|
realmfs: Default::default(),
|
||||||
|
colorscheme: Default::default(),
|
||||||
|
frame_color: Default::default(),
|
||||||
|
colorscheme_dialog: ColorSchemeDialog::new(),
|
||||||
|
options: Rc::new(RefCell::new(ConfigOptions::new())),
|
||||||
|
settings: RefCell::new(CitadelSettings::new()),
|
||||||
|
bool_option_rows: RefCell::new(Vec::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for ConfigureDialog {
|
||||||
|
const NAME: &'static str = "ConfigureDialog";
|
||||||
|
type Type = super::ConfigureDialog;
|
||||||
|
type ParentType = gtk::Dialog;
|
||||||
|
|
||||||
|
fn class_init(klass: &mut Self::Class) {
|
||||||
|
Self::bind_template(klass);
|
||||||
|
}
|
||||||
|
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||||
|
obj.init_template();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectImpl for ConfigureDialog {
|
||||||
|
fn constructed(&self, obj: &Self::Type) {
|
||||||
|
self.parent_constructed(obj);
|
||||||
|
self.colorscheme_dialog.set_transient_for(Some(&self.instance()));
|
||||||
|
self.setup_widgets();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DialogImpl for ConfigureDialog {}
|
||||||
|
impl WindowImpl for ConfigureDialog {}
|
||||||
|
impl BinImpl for ConfigureDialog {}
|
||||||
|
impl ContainerImpl for ConfigureDialog {}
|
||||||
|
impl WidgetImpl for ConfigureDialog {}
|
||||||
78
realm-config-ui/src/configure_dialog/mod.rs
Normal file
78
realm-config-ui/src/configure_dialog/mod.rs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
use gtk::glib;
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use glib::subclass::prelude::*;
|
||||||
|
|
||||||
|
use crate::realmsd::RealmConfig;
|
||||||
|
pub use crate::configure_dialog::options::{ConfigOptions,BoolOption};
|
||||||
|
|
||||||
|
mod dialog;
|
||||||
|
mod option_row;
|
||||||
|
mod options;
|
||||||
|
mod settings;
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct ConfigureDialog(ObjectSubclass<dialog::ConfigureDialog>)
|
||||||
|
@extends gtk::Dialog, gtk::Window, gtk::Bin, gtk::Container, gtk::Widget,
|
||||||
|
@implements gtk::Buildable;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigureDialog {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
glib::Object::new(&[("use-header-bar", &1)])
|
||||||
|
.expect("Failed to create ConfigureDialog")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instance(&self) -> &dialog::ConfigureDialog {
|
||||||
|
dialog::ConfigureDialog::from_instance(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn changes(&self) -> Vec<(String,String)> {
|
||||||
|
self.instance().changes()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn store_settings(&self, realm_name: &str) {
|
||||||
|
self.instance().store_settings(realm_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset_options(&self) {
|
||||||
|
self.instance().reset_options();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_realm_name(&self, name: &str) {
|
||||||
|
self.set_title(&format!("Configure realm-{}", name));
|
||||||
|
self.instance().set_realm_name(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_config(&self, config: &RealmConfig) {
|
||||||
|
self.instance().set_config(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct ConfigureOption(ObjectSubclass<option_row::ConfigureOption>)
|
||||||
|
@extends gtk::Widget, gtk::Bin, gtk::Container,
|
||||||
|
@implements gtk::Buildable, gtk::Actionable;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigureOption {
|
||||||
|
pub fn new(option: &BoolOption) -> Self {
|
||||||
|
let widget :Self = glib::Object::new(&[])
|
||||||
|
.expect("Failed to create ConfigureOption");
|
||||||
|
widget.set_bool_option(option);
|
||||||
|
widget
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instance(&self) -> &option_row::ConfigureOption {
|
||||||
|
option_row::ConfigureOption::from_instance(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&self) {
|
||||||
|
self.instance().update();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_bool_option(&self, option: &BoolOption) {
|
||||||
|
self.set_tooltip_markup(Some(option.tooltip()));
|
||||||
|
self.instance().set_bool_option(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
68
realm-config-ui/src/configure_dialog/option_row.rs
Normal file
68
realm-config-ui/src/configure_dialog/option_row.rs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
use gtk::glib;
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use gtk::subclass::prelude::*;
|
||||||
|
use gtk::CompositeTemplate;
|
||||||
|
|
||||||
|
use crate::configure_dialog::BoolOption;
|
||||||
|
|
||||||
|
#[derive(CompositeTemplate)]
|
||||||
|
#[template(file = "configure-option-switch.ui")]
|
||||||
|
pub struct ConfigureOption {
|
||||||
|
#[template_child]
|
||||||
|
pub name: TemplateChild<gtk::Label>,
|
||||||
|
#[template_child]
|
||||||
|
pub switch: TemplateChild<gtk::Switch>,
|
||||||
|
|
||||||
|
pub option: RefCell<Option<BoolOption>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ConfigureOption {
|
||||||
|
fn default() -> Self {
|
||||||
|
ConfigureOption {
|
||||||
|
name: Default::default(),
|
||||||
|
switch: Default::default(),
|
||||||
|
option: RefCell::new(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigureOption {
|
||||||
|
pub fn set_bool_option(&self, option: &BoolOption) {
|
||||||
|
self.name.set_text(option.description());
|
||||||
|
self.switch.set_state(option.value());
|
||||||
|
self.switch.connect_state_set(glib::clone!(@strong option => move |_b,v| {
|
||||||
|
option.set_value(v);
|
||||||
|
Inhibit(false)
|
||||||
|
}));
|
||||||
|
self.option.borrow_mut().replace(option.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&self) {
|
||||||
|
let option = self.option.borrow();
|
||||||
|
if let Some(option) = option.as_ref() {
|
||||||
|
self.switch.set_state(option.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for ConfigureOption {
|
||||||
|
const NAME: &'static str = "ConfigureOption";
|
||||||
|
type Type = super::ConfigureOption;
|
||||||
|
type ParentType = gtk::ListBoxRow;
|
||||||
|
|
||||||
|
fn class_init(klass: &mut Self::Class) {
|
||||||
|
Self::bind_template(klass);
|
||||||
|
}
|
||||||
|
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||||
|
obj.init_template();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectImpl for ConfigureOption {}
|
||||||
|
impl WidgetImpl for ConfigureOption {}
|
||||||
|
impl ContainerImpl for ConfigureOption {}
|
||||||
|
impl BinImpl for ConfigureOption {}
|
||||||
|
impl ListBoxRowImpl for ConfigureOption {}
|
||||||
384
realm-config-ui/src/configure_dialog/options.rs
Normal file
384
realm-config-ui/src/configure_dialog/options.rs
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
use std::cell::Cell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use libcitadel::OverlayType;
|
||||||
|
use libcitadel::terminal::Base16Scheme;
|
||||||
|
|
||||||
|
use crate::realmsd::RealmConfig;
|
||||||
|
|
||||||
|
const GPU_TOOLTIP: &str = r#"If enabled the render node device <tt><b>/dev/dri/renderD128</b></tt> will be mounted into the realm container.
|
||||||
|
|
||||||
|
If privileged device <tt><b>/dev/dri/card0</b></tt> is also needed set
|
||||||
|
additional variable in realm configuration file:
|
||||||
|
|
||||||
|
<tt><b>use-gpu-card0 = true</b></tt>
|
||||||
|
|
||||||
|
"#;
|
||||||
|
const WAYLAND_TOOLTIP: &str = "\
|
||||||
|
If enabled access to Wayland display will be permitted in realm by adding wayland socket to realm.
|
||||||
|
|
||||||
|
<tt><b>/run/user/1000/wayland-0</b></tt>
|
||||||
|
|
||||||
|
";
|
||||||
|
|
||||||
|
const X11_TOOLTIP: &str = "\
|
||||||
|
If enabled access to X11 server will be added by mounting directory X11 directory into realm.
|
||||||
|
|
||||||
|
<tt><b>/tmp/.X11-unix</b></tt>
|
||||||
|
";
|
||||||
|
|
||||||
|
const SOUND_TOOLTIP: &str = r#"If enabled allows use of sound inside of realm. The following items will be added:
|
||||||
|
|
||||||
|
<tt><b>/dev/snd</b></tt>
|
||||||
|
<tt><b>/dev/shm</b></tt>
|
||||||
|
<tt><b>/run/user/1000/pulse</b></tt>
|
||||||
|
"#;
|
||||||
|
|
||||||
|
const SHARED_DIR_TOOLTIP: &str = r#"If enabled the shared directory will be mounted as <tt><b>/Shared</b></tt> in home directory of realm.
|
||||||
|
|
||||||
|
This directory is shared between all realms with this option enabled and is an easy way to move files between realms.
|
||||||
|
"#;
|
||||||
|
|
||||||
|
const NETWORK_TOOLTIP: &str = "\
|
||||||
|
If enabled the realm will have access to the network.
|
||||||
|
";
|
||||||
|
|
||||||
|
const KVM_TOOLTIP: &str = r#"If enabled device <tt><b>/dev/kvm</b></tt> will be added to realm.
|
||||||
|
|
||||||
|
This allows use of applications such as Qemu inside of realms.
|
||||||
|
"#;
|
||||||
|
|
||||||
|
const EPHERMERAL_HOME_TOOLTIP: &str = r#"If enabled the home directory of realm will be set up in ephemeral mode.
|
||||||
|
|
||||||
|
The ephemeral home directory is set up with the following steps:
|
||||||
|
|
||||||
|
1. Home directory is mounted as tmpfs filesystem
|
||||||
|
2. Any files in <tt><b>/realms/skel</b></tt> are copied into home directory
|
||||||
|
3. Any files in <tt><b>/realms/realm-$name/skel</b></tt> are copied into home directory.
|
||||||
|
4. Any directories listed in config file variable <tt><b>ephemeral_persistent_dirs</b></tt>
|
||||||
|
are bind mounted from <tt><b>/realms/realm-$name/home</b></tt> into ephemeral
|
||||||
|
home directory.
|
||||||
|
"#;
|
||||||
|
|
||||||
|
const BOOL_OPTIONS: &[(&str, &str, &str)] = &[
|
||||||
|
("use-gpu", "Use GPU in Realm", GPU_TOOLTIP),
|
||||||
|
("use-wayland", "Use Wayland in Realm", WAYLAND_TOOLTIP),
|
||||||
|
("use-x11", "Use X11 in Realm", X11_TOOLTIP),
|
||||||
|
("use-sound", "Use Sound in Realm", SOUND_TOOLTIP),
|
||||||
|
("use-shared-dir", "Mount /Shared directory in Realm", SHARED_DIR_TOOLTIP),
|
||||||
|
("use-network", "Realm has network access", NETWORK_TOOLTIP),
|
||||||
|
("use-kvm", "Use KVM (/dev/kvm) in Realm", KVM_TOOLTIP),
|
||||||
|
("use-ephemeral-home", "Use ephemeral tmpfs mount for home directory", EPHERMERAL_HOME_TOOLTIP),
|
||||||
|
];
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BoolOption {
|
||||||
|
id: String,
|
||||||
|
description: String,
|
||||||
|
tooltip: String,
|
||||||
|
original: Rc<Cell<bool>>,
|
||||||
|
value: Rc<Cell<bool>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BoolOption {
|
||||||
|
fn create_options() -> Vec<BoolOption> {
|
||||||
|
let mut bools = Vec::new();
|
||||||
|
for (id, description, tooltip) in BOOL_OPTIONS {
|
||||||
|
bools.push(BoolOption::new(id, description, tooltip));
|
||||||
|
}
|
||||||
|
bools
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(id: &str, description: &str, tooltip: &str) -> Self {
|
||||||
|
let id = id.to_string();
|
||||||
|
let description = description.to_string();
|
||||||
|
let tooltip = format!("<b><big>{}</big></b>\n\n{}", description, tooltip);
|
||||||
|
let value = Rc::new(Cell::new(false));
|
||||||
|
let original = Rc::new(Cell::new(false));
|
||||||
|
BoolOption { id, description, tooltip, original, value }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn value(&self) -> bool {
|
||||||
|
self.value.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_changed(&self) -> bool {
|
||||||
|
self.value() != self.original.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_value(&self, v: bool) {
|
||||||
|
self.value.set(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> &str {
|
||||||
|
&self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn description(&self) -> &str {
|
||||||
|
&self.description
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tooltip(&self) -> &str {
|
||||||
|
&self.tooltip
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure(&self, config: &RealmConfig) {
|
||||||
|
let v = config.get_bool(self.id());
|
||||||
|
self.original.set(v);
|
||||||
|
self.value.set(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&self) {
|
||||||
|
self.set_value(self.original.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_changes(&self, result: &mut Vec<(String, String)>) {
|
||||||
|
if self.has_changed() {
|
||||||
|
let k = self.id.clone();
|
||||||
|
let v = self.value().to_string();
|
||||||
|
result.push((k, v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OverlayOption {
|
||||||
|
original: OverlayType,
|
||||||
|
current: OverlayType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OverlayOption {
|
||||||
|
fn new() -> Self {
|
||||||
|
OverlayOption {
|
||||||
|
original: OverlayType::None,
|
||||||
|
current: OverlayType::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn overlay_str_to_enum(str: Option<&str>) -> OverlayType {
|
||||||
|
match str {
|
||||||
|
Some("storage") => OverlayType::Storage,
|
||||||
|
Some("tmpfs") => OverlayType::TmpFS,
|
||||||
|
Some("none") => OverlayType::None,
|
||||||
|
None => OverlayType::None,
|
||||||
|
Some(s) => {
|
||||||
|
warn!("Unexpected overlay type: {}", s);
|
||||||
|
OverlayType::None
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_overlay(&mut self, overlay: &str) {
|
||||||
|
self.current = Self::overlay_str_to_enum(Some(overlay));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn str_value(&self) -> String {
|
||||||
|
self.current.to_str_value()
|
||||||
|
.unwrap_or("none").to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure(&mut self, config: &RealmConfig) {
|
||||||
|
let overlay = Self::overlay_str_to_enum(config.get_string("overlay"));
|
||||||
|
self.original = overlay;
|
||||||
|
self.current = overlay;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.current = self.original;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_changes(&self, result: &mut Vec<(String, String)>) {
|
||||||
|
if self.original != self.current {
|
||||||
|
let k = "overlay".to_string();
|
||||||
|
let v = self.str_value();
|
||||||
|
result.push((k, v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RealmFsOption {
|
||||||
|
original: String,
|
||||||
|
current: String,
|
||||||
|
realmfs_list: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RealmFsOption {
|
||||||
|
|
||||||
|
fn new() -> Self {
|
||||||
|
let base = String::from("base");
|
||||||
|
RealmFsOption {
|
||||||
|
original: base.clone(),
|
||||||
|
current: base.clone(),
|
||||||
|
realmfs_list: vec![base],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn realmfs_list(&self) -> Vec<String> {
|
||||||
|
self.realmfs_list.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current(&self) -> String {
|
||||||
|
self.current.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_current(&mut self, realmfs: &str) {
|
||||||
|
self.current = realmfs.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure(&mut self, config: &RealmConfig) {
|
||||||
|
if let Some(realmfs) = config.get_string("realmfs") {
|
||||||
|
|
||||||
|
self.realmfs_list.clear();
|
||||||
|
self.realmfs_list.extend(config.realmfs_list().iter().cloned());
|
||||||
|
self.original = realmfs.to_string();
|
||||||
|
self.current = realmfs.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.current = self.original.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_changes(&self, result: &mut Vec<(String, String)>) {
|
||||||
|
if self.current.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.current != self.original {
|
||||||
|
result.push(("realmfs".to_string(), self.current.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_SCHEME: &str = "default-dark";
|
||||||
|
|
||||||
|
struct ColorSchemeOption {
|
||||||
|
original: Base16Scheme,
|
||||||
|
current: Base16Scheme,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorSchemeOption {
|
||||||
|
fn new() -> Self {
|
||||||
|
let scheme = Base16Scheme::by_name(DEFAULT_SCHEME)
|
||||||
|
.expect("default Base16Scheme");
|
||||||
|
|
||||||
|
ColorSchemeOption {
|
||||||
|
original: scheme.clone(),
|
||||||
|
current: scheme.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure(&mut self, config: &RealmConfig) {
|
||||||
|
if let Some(scheme) = config.get_string("terminal-scheme") {
|
||||||
|
if let Some(scheme) = Base16Scheme::by_name(scheme) {
|
||||||
|
self.original = scheme.clone();
|
||||||
|
self.current = scheme.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.set_current(self.original.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_current(&mut self, scheme: Base16Scheme) {
|
||||||
|
self.current = scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_current_id(&mut self, id: &str) {
|
||||||
|
if let Some(scheme) = Base16Scheme::by_name(id) {
|
||||||
|
self.set_current(scheme.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current(&self) -> Base16Scheme {
|
||||||
|
self.current.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_changes(&self, result: &mut Vec<(String, String)>) {
|
||||||
|
if self.original.slug() != self.current.slug() {
|
||||||
|
result.push(("terminal-scheme".to_string(), self.current.slug().to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ConfigOptions {
|
||||||
|
bool_options: Vec<BoolOption>,
|
||||||
|
overlay: OverlayOption,
|
||||||
|
realmfs: RealmFsOption,
|
||||||
|
colorscheme: ColorSchemeOption,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigOptions {
|
||||||
|
|
||||||
|
pub fn configure(&mut self, config: &RealmConfig) {
|
||||||
|
for op in &self.bool_options {
|
||||||
|
op.configure(config);
|
||||||
|
}
|
||||||
|
self.overlay.configure(config);
|
||||||
|
self.realmfs.configure(config);
|
||||||
|
self.colorscheme.configure(config);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
for op in &self.bool_options {
|
||||||
|
op.reset();
|
||||||
|
}
|
||||||
|
self.overlay.reset();
|
||||||
|
self.realmfs.reset();
|
||||||
|
self.colorscheme.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn changes(&self) -> Vec<(String,String)> {
|
||||||
|
let mut changes = Vec::new();
|
||||||
|
for op in &self.bool_options {
|
||||||
|
op.add_changes(&mut changes);
|
||||||
|
}
|
||||||
|
self.overlay.add_changes(&mut changes);
|
||||||
|
self.realmfs.add_changes(&mut changes);
|
||||||
|
self.colorscheme.add_changes(&mut changes);
|
||||||
|
changes
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let bool_options = BoolOption::create_options();
|
||||||
|
let overlay = OverlayOption::new();
|
||||||
|
let realmfs = RealmFsOption::new();
|
||||||
|
let colorscheme = ColorSchemeOption::new();
|
||||||
|
ConfigOptions {
|
||||||
|
bool_options, overlay, realmfs, colorscheme,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bool_options(&self) -> &[BoolOption] {
|
||||||
|
&self.bool_options
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn realmfs_list(&self) -> Vec<String> {
|
||||||
|
self.realmfs.realmfs_list()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn overlay_id(&self) -> String {
|
||||||
|
self.overlay.str_value()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_overlay_id(&mut self, id: &str) {
|
||||||
|
self.overlay.set_overlay(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn realmfs(&self) -> String {
|
||||||
|
self.realmfs.current()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_realmfs(&mut self, realmfs: &str) {
|
||||||
|
self.realmfs.set_current(realmfs);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn colorscheme(&self) -> Base16Scheme {
|
||||||
|
self.colorscheme.current()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_colorscheme_id(&mut self, id: &str) {
|
||||||
|
self.colorscheme.set_current_id(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
126
realm-config-ui/src/configure_dialog/settings.rs
Normal file
126
realm-config-ui/src/configure_dialog/settings.rs
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
use gtk::{gdk,gio};
|
||||||
|
use gtk::gio::prelude::*;
|
||||||
|
use rand::Rng;
|
||||||
|
use libcitadel::Realm;
|
||||||
|
|
||||||
|
pub struct CitadelSettings {
|
||||||
|
settings: gio::Settings,
|
||||||
|
frame_colors: Vec<gdk::RGBA>,
|
||||||
|
realms: Vec<RealmFrameColor>,
|
||||||
|
used_colors: HashSet<gdk::RGBA>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct RealmFrameColor(String,gdk::RGBA);
|
||||||
|
|
||||||
|
impl RealmFrameColor {
|
||||||
|
|
||||||
|
fn new(realm: &str, color: &gdk::RGBA) -> Self {
|
||||||
|
RealmFrameColor(realm.to_string(), color.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn realm(&self) -> &str {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color(&self) -> &gdk::RGBA {
|
||||||
|
&self.1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_color(&mut self, color: gdk::RGBA) {
|
||||||
|
self.1 = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CitadelSettings {
|
||||||
|
|
||||||
|
fn choose_random_color(&self) -> gdk::RGBA {
|
||||||
|
if !self.frame_colors.is_empty() {
|
||||||
|
let n = rand::thread_rng().gen_range(0..self.frame_colors.len());
|
||||||
|
self.frame_colors[n].clone()
|
||||||
|
} else {
|
||||||
|
gdk::RGBA::blue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn allocate_color(&self) -> gdk::RGBA {
|
||||||
|
self.frame_colors.iter()
|
||||||
|
.find(|&c| !self.used_colors.contains(c))
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| self.choose_random_color())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_realm_color(&self, name: Option<&str>) -> gdk::RGBA {
|
||||||
|
name.and_then(|name| self.get_realm_frame_color(name))
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| self.allocate_color())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn store_realm_color(&mut self, name: &str, color: gdk::RGBA) -> bool {
|
||||||
|
if let Some(realm) = self.realms.iter_mut().find(|r| r.realm() == name) {
|
||||||
|
realm.set_color(color);
|
||||||
|
} else {
|
||||||
|
self.realms.push(RealmFrameColor::new(name, &color));
|
||||||
|
}
|
||||||
|
|
||||||
|
let list = self.realms.iter().map(|r| r.to_string()).collect::<Vec<String>>();
|
||||||
|
let realms = list.iter().map(|s| s.as_str()).collect::<Vec<&str>>();
|
||||||
|
self.settings.set_strv("realm-label-colors", &realms).is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let settings = gio::Settings::new("com.subgraph.citadel");
|
||||||
|
|
||||||
|
let realms = settings.strv("realm-label-colors")
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|gs| RealmFrameColor::try_from(gs.as_str()).ok())
|
||||||
|
.collect::<Vec<RealmFrameColor>>();
|
||||||
|
|
||||||
|
let frame_colors = settings.strv("label-color-list").into_iter()
|
||||||
|
.flat_map(|gs| gs.as_str().parse().ok())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let used_colors = realms.iter()
|
||||||
|
.map(|rfc| rfc.1.clone()).collect();
|
||||||
|
|
||||||
|
CitadelSettings {
|
||||||
|
settings,
|
||||||
|
frame_colors,
|
||||||
|
realms,
|
||||||
|
used_colors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_realm_frame_color(&self, name: &str) -> Option<&gdk::RGBA> {
|
||||||
|
self.realms.iter()
|
||||||
|
.find(|r| r.realm() == name)
|
||||||
|
.map(|r| r.color())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&str> for RealmFrameColor {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||||
|
let idx = value.find(':').ok_or(())?;
|
||||||
|
let (realm, color_str) = value.split_at(idx);
|
||||||
|
|
||||||
|
let rgba = &color_str[1..].parse::<gdk::RGBA>()
|
||||||
|
.map_err(|_| ())?;
|
||||||
|
|
||||||
|
if Realm::is_valid_name(realm) {
|
||||||
|
Ok(RealmFrameColor::new(realm, rgba))
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for RealmFrameColor {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
format!("{}:{}", self.realm(), self.color())
|
||||||
|
}
|
||||||
|
}
|
||||||
60
realm-config-ui/src/error.rs
Normal file
60
realm-config-ui/src/error.rs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
use std::result;
|
||||||
|
use std::fmt;
|
||||||
|
use crate::error::Error::Zbus;
|
||||||
|
use std::fmt::Formatter;
|
||||||
|
use gtk::prelude::*;
|
||||||
|
|
||||||
|
pub type Result<T> = result::Result<T, Error>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
Zbus(zbus::Error),
|
||||||
|
ManagerConnect,
|
||||||
|
NoSuchRealm(String),
|
||||||
|
CreateRealmFailed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
fn create_dialog(&self) -> gtk::MessageDialog {
|
||||||
|
let title = "Error";
|
||||||
|
let message = self.to_string();
|
||||||
|
|
||||||
|
gtk::MessageDialog::builder()
|
||||||
|
.message_type(gtk::MessageType::Error)
|
||||||
|
.title(title)
|
||||||
|
.text(&message)
|
||||||
|
.buttons(gtk::ButtonsType::Close)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn error_dialog<P: IsA<gtk::Window>>(&self, parent: Option<&P>) {
|
||||||
|
let dialog = self.create_dialog();
|
||||||
|
dialog.set_transient_for(parent);
|
||||||
|
dialog.run();
|
||||||
|
dialog.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn app_error_dialog(&self, app: >k::Application) {
|
||||||
|
let dialog = self.create_dialog();
|
||||||
|
app.add_window(&dialog);
|
||||||
|
dialog.run();
|
||||||
|
dialog.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Error::Zbus(e) => write!(f, "ZBus error: {}", e),
|
||||||
|
Error::ManagerConnect => write!(f, "Unable to connect to Realms Manager"),
|
||||||
|
Error::NoSuchRealm(name) => write!(f, "Realm '{}' does not exist", name),
|
||||||
|
Error::CreateRealmFailed => write!(f, "Failed to create new realm"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<zbus::Error> for Error {
|
||||||
|
fn from(e: zbus::Error) -> Self {
|
||||||
|
Zbus(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
120
realm-config-ui/src/main.rs
Normal file
120
realm-config-ui/src/main.rs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
#[macro_use] extern crate libcitadel;
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use gtk::gio;
|
||||||
|
|
||||||
|
use crate::configure_dialog::ConfigureDialog;
|
||||||
|
use crate::new_realm::NewRealmDialog;
|
||||||
|
use crate::error::Result;
|
||||||
|
use crate::realmsd::{RealmConfig, RealmsManagerProxy};
|
||||||
|
|
||||||
|
mod realmsd;
|
||||||
|
mod error;
|
||||||
|
mod colorscheme;
|
||||||
|
mod configure_dialog;
|
||||||
|
mod new_realm;
|
||||||
|
|
||||||
|
|
||||||
|
fn load_realm_names() -> Result<(RealmsManagerProxy<'static>, Vec<String>, RealmConfig)> {
|
||||||
|
let manager = RealmsManagerProxy::connect()?;
|
||||||
|
let names = manager.realm_names()?;
|
||||||
|
let config = manager.default_config()?;
|
||||||
|
Ok((manager, names, config))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_realm_ui(app: >k::Application) {
|
||||||
|
let (manager, realms, config) = match load_realm_names() {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(err) => {
|
||||||
|
err.app_error_dialog(app);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let dialog = NewRealmDialog::new();
|
||||||
|
dialog.set_realm_names(&realms);
|
||||||
|
dialog.set_config(&config);
|
||||||
|
app.add_window(&dialog);
|
||||||
|
dialog.show_all();
|
||||||
|
|
||||||
|
if dialog.run() == gtk::ResponseType::Ok {
|
||||||
|
let realm = dialog.get_realm_name();
|
||||||
|
dialog.store_config_settings();
|
||||||
|
let changes = dialog.config_changes();
|
||||||
|
if let Err(err) = manager.create_new_realm(&realm, changes) {
|
||||||
|
err.error_dialog(Some(&dialog));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dialog.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_realm_config(realm_name: &str) -> Result<(RealmsManagerProxy<'static>, RealmConfig)> {
|
||||||
|
let manager = RealmsManagerProxy::connect()?;
|
||||||
|
let config = manager.config(realm_name)?;
|
||||||
|
Ok((manager, config))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure_realm_ui(app: >k::Application, name: &str) {
|
||||||
|
let (manager, config) = match load_realm_config(name) {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(err) => {
|
||||||
|
err.app_error_dialog(app);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let dialog = ConfigureDialog::new();
|
||||||
|
app.add_window(&dialog);
|
||||||
|
dialog.set_config(&config);
|
||||||
|
dialog.set_realm_name(name);
|
||||||
|
dialog.show_all();
|
||||||
|
|
||||||
|
if dialog.run() == gtk::ResponseType::Ok {
|
||||||
|
dialog.store_settings(name);
|
||||||
|
let changes = dialog.changes();
|
||||||
|
if !changes.is_empty() {
|
||||||
|
if let Err(err) = manager.configure_realm(name, changes) {
|
||||||
|
err.error_dialog(Some(&dialog));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dialog.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_ui(app: >k::Application) {
|
||||||
|
let config = RealmConfig::new_default(vec![String::from("main"), String::from("foo")]);
|
||||||
|
let dialog = ConfigureDialog::new();
|
||||||
|
app.add_window(&dialog);
|
||||||
|
dialog.set_config(&config);
|
||||||
|
dialog.set_title("Configure realm-testing");
|
||||||
|
dialog.show_all();
|
||||||
|
|
||||||
|
if dialog.run() == gtk::ResponseType::Ok {
|
||||||
|
let changes = dialog.changes();
|
||||||
|
println!("Changes: {:?}", changes);
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
|
||||||
|
let mut args = env::args().collect::<Vec<String>>();
|
||||||
|
|
||||||
|
|
||||||
|
if args.len() > 1 {
|
||||||
|
let first = args.remove(1);
|
||||||
|
let application = gtk::Application::new(Some("com.subgraph.RealmConfig"), gio::ApplicationFlags::empty());
|
||||||
|
if first.as_str() == "--new" {
|
||||||
|
application.connect_activate(new_realm_ui);
|
||||||
|
} else if first.as_str() == "--test" {
|
||||||
|
application.connect_activate(test_ui);
|
||||||
|
} else {
|
||||||
|
application.connect_activate(move |app| {
|
||||||
|
configure_realm_ui(app, &first);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
application.run_with_args(&args);
|
||||||
|
}
|
||||||
|
}
|
||||||
134
realm-config-ui/src/new_realm/dialog.rs
Normal file
134
realm-config-ui/src/new_realm/dialog.rs
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use gtk::glib;
|
||||||
|
use gtk::CompositeTemplate;
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use gtk::subclass::prelude::*;
|
||||||
|
|
||||||
|
use crate::configure_dialog::ConfigureDialog;
|
||||||
|
use crate::new_realm::verifier::RealmNameVerifier;
|
||||||
|
use crate::realmsd::RealmConfig;
|
||||||
|
|
||||||
|
#[derive(CompositeTemplate)]
|
||||||
|
#[template(file = "new-realm-dialog.ui")]
|
||||||
|
pub struct NewRealmDialog {
|
||||||
|
#[template_child]
|
||||||
|
pub infobar: TemplateChild<gtk::InfoBar>,
|
||||||
|
|
||||||
|
#[template_child]
|
||||||
|
pub infolabel: TemplateChild<gtk::Label>,
|
||||||
|
|
||||||
|
#[template_child]
|
||||||
|
pub label: TemplateChild<gtk::Label>,
|
||||||
|
|
||||||
|
#[template_child]
|
||||||
|
entry: TemplateChild<gtk::Entry>,
|
||||||
|
|
||||||
|
#[template_child (id="config-button")]
|
||||||
|
pub config_button: TemplateChild<gtk::Button>,
|
||||||
|
|
||||||
|
pub realm_names: Rc<RefCell<Vec<String>>>,
|
||||||
|
|
||||||
|
configure_dialog: ConfigureDialog,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NewRealmDialog {
|
||||||
|
fn default() -> Self {
|
||||||
|
NewRealmDialog {
|
||||||
|
infobar: Default::default(),
|
||||||
|
infolabel: Default::default(),
|
||||||
|
label: Default::default(),
|
||||||
|
entry: Default::default(),
|
||||||
|
config_button: Default::default(),
|
||||||
|
realm_names: Default::default(),
|
||||||
|
configure_dialog: ConfigureDialog::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NewRealmDialog {
|
||||||
|
pub fn set_realm_names(&self, names: &[String]) {
|
||||||
|
let mut lock = self.realm_names.borrow_mut();
|
||||||
|
lock.clear();
|
||||||
|
lock.extend_from_slice(&names)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_config(&self, config: &RealmConfig) {
|
||||||
|
self.configure_dialog.set_config(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_realm_name(&self) -> String {
|
||||||
|
self.entry.text().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn config_changes(&self) -> Vec<(String,String)> {
|
||||||
|
self.configure_dialog.changes()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn store_config_settings(&self) {
|
||||||
|
let realm_name = self.get_realm_name();
|
||||||
|
if !realm_name.is_empty() {
|
||||||
|
self.configure_dialog.store_settings(&realm_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for NewRealmDialog {
|
||||||
|
const NAME: &'static str = "NewRealmDialog";
|
||||||
|
type Type = super::NewRealmDialog;
|
||||||
|
type ParentType = gtk::Dialog;
|
||||||
|
|
||||||
|
fn class_init(klass: &mut Self::Class) {
|
||||||
|
Self::bind_template(klass);
|
||||||
|
}
|
||||||
|
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||||
|
obj.init_template();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectImpl for NewRealmDialog {
|
||||||
|
fn constructed(&self, obj: &Self::Type) {
|
||||||
|
self.parent_constructed(obj);
|
||||||
|
|
||||||
|
self.configure_dialog.set_transient_for(Some(&self.instance()));
|
||||||
|
let verifier = Rc::new(RealmNameVerifier::new(self));
|
||||||
|
|
||||||
|
self.entry.connect_insert_text(glib::clone!(@strong verifier => move |entry, text, pos|{
|
||||||
|
if !verifier.verify_insert(entry, text, *pos) {
|
||||||
|
entry.stop_signal_emission("insert-text");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
self.entry.connect_delete_text(glib::clone!(@strong verifier => move |entry, start, end| {
|
||||||
|
if !verifier.verify_delete(entry, start, end) {
|
||||||
|
entry.stop_signal_emission("delete-text");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
self.entry.connect_changed(glib::clone!(@strong verifier => move |entry| {
|
||||||
|
verifier.changed(entry);
|
||||||
|
}));
|
||||||
|
|
||||||
|
let config_dialog = self.configure_dialog.clone();
|
||||||
|
let entry = self.entry.clone();
|
||||||
|
self.config_button.connect_clicked(move |_b| {
|
||||||
|
let name = entry.text().to_string();
|
||||||
|
config_dialog.set_title(&format!("Configure realm-{}", name));
|
||||||
|
config_dialog.show_all();
|
||||||
|
match config_dialog.run() {
|
||||||
|
gtk::ResponseType::Ok => {},
|
||||||
|
_ => config_dialog.reset_options(),
|
||||||
|
}
|
||||||
|
config_dialog.hide();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DialogImpl for NewRealmDialog {}
|
||||||
|
impl WindowImpl for NewRealmDialog {}
|
||||||
|
impl BinImpl for NewRealmDialog {}
|
||||||
|
impl ContainerImpl for NewRealmDialog {}
|
||||||
|
impl WidgetImpl for NewRealmDialog {}
|
||||||
44
realm-config-ui/src/new_realm/mod.rs
Normal file
44
realm-config-ui/src/new_realm/mod.rs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
use gtk::glib;
|
||||||
|
use glib::subclass::prelude::*;
|
||||||
|
|
||||||
|
use crate::realmsd::RealmConfig;
|
||||||
|
|
||||||
|
mod dialog;
|
||||||
|
mod verifier;
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct NewRealmDialog(ObjectSubclass<dialog::NewRealmDialog>)
|
||||||
|
@extends gtk::Dialog, gtk::Window, gtk::Bin, gtk::Container, gtk::Widget,
|
||||||
|
@implements gtk::Buildable;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NewRealmDialog {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
glib::Object::new(&[("use-header-bar", &1)])
|
||||||
|
.expect("Failed to create NewRealmDialog")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instance(&self) -> &dialog::NewRealmDialog {
|
||||||
|
dialog::NewRealmDialog::from_instance(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_realm_names(&self, names: &[String]) {
|
||||||
|
self.instance().set_realm_names(names);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_config(&self, config: &RealmConfig) {
|
||||||
|
self.instance().set_config(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_realm_name(&self) -> String {
|
||||||
|
self.instance().get_realm_name()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn config_changes(&self) -> Vec<(String,String)> {
|
||||||
|
self.instance().config_changes()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn store_config_settings(&self) {
|
||||||
|
self.instance().store_config_settings();
|
||||||
|
}
|
||||||
|
}
|
||||||
89
realm-config-ui/src/new_realm/new-realm-dialog.ui
Normal file
89
realm-config-ui/src/new_realm/new-realm-dialog.ui
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
<template class="NewRealmDialog" parent="GtkDialog">
|
||||||
|
<property name="title">Create New Realm</property>
|
||||||
|
<child internal-child="vbox">
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
|
||||||
|
<!-- GtkInfoBar -->
|
||||||
|
<child>
|
||||||
|
<object class="GtkInfoBar" id="infobar">
|
||||||
|
<property name="revealed">False</property>
|
||||||
|
<property name="message-type">warning</property>
|
||||||
|
<child internal-child="content_area">
|
||||||
|
<object class="GtkBox">
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="infolabel">
|
||||||
|
<property name="label">Name already exists</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<!-- GtkLabel -->
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="label">
|
||||||
|
<property name="label">Enter name for new realm:</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="margin-top">10</property>
|
||||||
|
<property name="margin-start">20</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<!-- GtkEntry-->
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<child>
|
||||||
|
<object class="GtkEntry" id="entry">
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="placeholder-text">Enter name of new realm</property>
|
||||||
|
<property name="margin-top">10</property>
|
||||||
|
<property name="margin-bottom">20</property>
|
||||||
|
<property name="margin-start">20</property>
|
||||||
|
<property name="margin-end">5</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<!-- GtkButton -->
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="config-button">
|
||||||
|
<property name="sensitive">False</property>
|
||||||
|
<property name="margin-top">10</property>
|
||||||
|
<property name="margin-bottom">20</property>
|
||||||
|
<property name="margin-start">5</property>
|
||||||
|
<property name="margin-end">20</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage">
|
||||||
|
<property name="icon-name">emblem-system-symbolic</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<child type="action">
|
||||||
|
<object class="GtkButton" id="cancel_button">
|
||||||
|
<property name="use-underline">1</property>
|
||||||
|
<property name="label">Cancel</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child type="action">
|
||||||
|
<object class="GtkButton" id="ok_button">
|
||||||
|
<property name="use-underline">1</property>
|
||||||
|
<property name="label">Create</property>
|
||||||
|
<property name="can-default">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<action-widgets>
|
||||||
|
<action-widget response="cancel">cancel_button</action-widget>
|
||||||
|
<action-widget response="ok" default="true">ok_button</action-widget>
|
||||||
|
</action-widgets>
|
||||||
|
</template>
|
||||||
|
</interface>
|
||||||
76
realm-config-ui/src/new_realm/verifier.rs
Normal file
76
realm-config-ui/src/new_realm/verifier.rs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use gtk::subclass::prelude::*;
|
||||||
|
|
||||||
|
use libcitadel::Realm;
|
||||||
|
|
||||||
|
use crate::new_realm::dialog::NewRealmDialog;
|
||||||
|
|
||||||
|
pub struct RealmNameVerifier {
|
||||||
|
ok: gtk::Widget,
|
||||||
|
infobar: gtk::InfoBar,
|
||||||
|
infolabel: gtk::Label,
|
||||||
|
label: gtk::Label,
|
||||||
|
config: gtk::Button,
|
||||||
|
realms: Rc<RefCell<Vec<String>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RealmNameVerifier {
|
||||||
|
pub fn new(dialog: &NewRealmDialog) -> Self {
|
||||||
|
let ok = dialog.instance().widget_for_response(gtk::ResponseType::Ok).expect("No Ok Widget found");
|
||||||
|
RealmNameVerifier {
|
||||||
|
ok,
|
||||||
|
infobar: dialog.infobar.clone(),
|
||||||
|
infolabel: dialog.infolabel.clone(),
|
||||||
|
label: dialog.label.clone(),
|
||||||
|
config: dialog.config_button.clone(),
|
||||||
|
realms: dialog.realm_names.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_insert(&self, entry: >k::Entry, text: &str, pos: i32) -> bool {
|
||||||
|
let mut s = entry.text().to_string();
|
||||||
|
s.insert_str(pos as usize, text);
|
||||||
|
Realm::is_valid_name(&s)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_delete(&self, entry: >k::Entry, start: i32, end: i32) -> bool {
|
||||||
|
let mut s = entry.text().to_string();
|
||||||
|
let start = start as usize;
|
||||||
|
let end = end as usize;
|
||||||
|
s.replace_range(start..end, "");
|
||||||
|
s.is_empty() || Realm::is_valid_name(&s)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_name (&self, name: &String) -> bool {
|
||||||
|
if self.realms.borrow().contains(name) {
|
||||||
|
self.infolabel.set_markup(&format!("Realm already exists with name <b>realm-{}</b>", name));
|
||||||
|
self.infobar.set_revealed(true);
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
self.infobar.set_revealed(false);
|
||||||
|
self.infolabel.set_markup("");
|
||||||
|
!name.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn changed(&self, entry: >k::Entry) {
|
||||||
|
let s = entry.text().to_string();
|
||||||
|
|
||||||
|
if self.verify_name(&s) {
|
||||||
|
self.ok.set_sensitive(true);
|
||||||
|
self.config.set_sensitive(true);
|
||||||
|
self.label.set_markup(&format!("<b>realm-{}</b>", s));
|
||||||
|
} else {
|
||||||
|
self.ok.set_sensitive(false);
|
||||||
|
self.config.set_sensitive(false);
|
||||||
|
if s.is_empty() {
|
||||||
|
self.label.set_markup("Enter name for new realm:");
|
||||||
|
} else {
|
||||||
|
self.label.set_markup("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
154
realm-config-ui/src/realmsd.rs
Normal file
154
realm-config-ui/src/realmsd.rs
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use zbus::dbus_proxy;
|
||||||
|
use zvariant::derive::Type;
|
||||||
|
use serde::{Serialize,Deserialize};
|
||||||
|
|
||||||
|
use crate::error::{Error, Result};
|
||||||
|
|
||||||
|
#[derive(Deserialize,Serialize,Type)]
|
||||||
|
pub struct RealmItem {
|
||||||
|
name: String,
|
||||||
|
description: String,
|
||||||
|
realmfs: String,
|
||||||
|
namespace: u64,
|
||||||
|
status: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RealmItem {
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug,Clone)]
|
||||||
|
pub struct RealmConfig {
|
||||||
|
options: Rc<HashMap<String,String>>,
|
||||||
|
realmfs_list: Rc<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RealmConfig {
|
||||||
|
pub fn new_default(realmfs_list: Vec<String>) -> Self {
|
||||||
|
let config = libcitadel::RealmConfig::default();
|
||||||
|
let mut vars = HashMap::new();
|
||||||
|
vars.insert("use-gpu".to_string(), config.gpu().to_string());
|
||||||
|
vars.insert("use-wayland".to_string(), config.wayland().to_string());
|
||||||
|
vars.insert("use-x11".to_string(), config.x11().to_string());
|
||||||
|
vars.insert("use-sound".to_string(), config.sound().to_string());
|
||||||
|
vars.insert("use-shared-dir".to_string(), config.shared_dir().to_string());
|
||||||
|
vars.insert("use-network".to_string(), config.network().to_string());
|
||||||
|
vars.insert("use-kvm".to_string(), config.kvm().to_string());
|
||||||
|
vars.insert("use-ephemeral-home".to_string(), config.ephemeral_home().to_string());
|
||||||
|
|
||||||
|
if realmfs_list.contains(&String::from("main")) {
|
||||||
|
vars.insert("realmfs".to_string(), String::from("main"));
|
||||||
|
} else if let Some(first) = realmfs_list.first() {
|
||||||
|
vars.insert("realmfs".to_string(), first.clone());
|
||||||
|
}
|
||||||
|
Self::new(vars, realmfs_list)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(options: HashMap<String, String>, realmfs_list: Vec<String>) -> Self {
|
||||||
|
RealmConfig {
|
||||||
|
options: Rc::new(options),
|
||||||
|
realmfs_list: Rc::new(realmfs_list),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_string(&self, id: &str) -> Option<&str> {
|
||||||
|
self.options.get(id).map(|s| s.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_bool(val: &str) -> bool {
|
||||||
|
match val.parse::<bool>() {
|
||||||
|
Ok(v) => v,
|
||||||
|
_ => {
|
||||||
|
warn!("Failed to parse value '{}' as bool", val);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_bool(&self, id: &str) -> bool {
|
||||||
|
match self.get_string(id) {
|
||||||
|
Some(val) => Self::parse_bool(val),
|
||||||
|
None => {
|
||||||
|
warn!("No value found for option '{}'", id);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn realmfs_list(&self) -> &[String] {
|
||||||
|
&self.realmfs_list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[dbus_proxy(
|
||||||
|
default_service = "com.subgraph.realms",
|
||||||
|
interface = "com.subgraph.realms.Manager",
|
||||||
|
default_path = "/com/subgraph/realms"
|
||||||
|
)]
|
||||||
|
pub trait RealmsManager {
|
||||||
|
fn get_current(&self) -> zbus::Result<String>;
|
||||||
|
fn realm_set_config(&self, name: &str, vars: Vec<(String,String)>) -> zbus::Result<()>;
|
||||||
|
fn list(&self) -> zbus::Result<Vec<RealmItem>>;
|
||||||
|
fn realm_config(&self, name: &str) -> zbus::Result<HashMap<String,String>>;
|
||||||
|
fn realm_exists(&self, name: &str) -> zbus::Result<bool>;
|
||||||
|
fn list_realm_f_s(&self) -> zbus::Result<Vec<String>>;
|
||||||
|
fn create_realm(&self, name: &str) -> zbus::Result<bool>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RealmsManagerProxy<'_> {
|
||||||
|
pub fn connect() -> Result<Self> {
|
||||||
|
let connection = zbus::Connection::new_system()?;
|
||||||
|
|
||||||
|
let proxy = RealmsManagerProxy::new(&connection)
|
||||||
|
.map_err(|_| Error::ManagerConnect)?;
|
||||||
|
|
||||||
|
// Test connection
|
||||||
|
proxy.get_current().map_err(|_| Error::ManagerConnect)?;
|
||||||
|
|
||||||
|
Ok(proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn realm_names(&self) -> Result<Vec<String>> {
|
||||||
|
let realms = self.list()?;
|
||||||
|
let names = realms.iter()
|
||||||
|
.map(|r| r.name().to_string())
|
||||||
|
.collect();
|
||||||
|
Ok(names)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_config(&self) -> Result<RealmConfig> {
|
||||||
|
let realmfs_list = self.list_realm_f_s()?;
|
||||||
|
Ok(RealmConfig::new_default(realmfs_list))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn config(&self, realm: &str) -> Result<RealmConfig> {
|
||||||
|
if !self.realm_exists(realm)? {
|
||||||
|
return Err(Error::NoSuchRealm(realm.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let options = self.realm_config(realm)?;
|
||||||
|
let realmfs_list = self.list_realm_f_s()?;
|
||||||
|
Ok(RealmConfig::new(options, realmfs_list))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn configure_realm(&self, realm: &str, config: Vec<(String, String)>) -> Result<()> {
|
||||||
|
self.realm_set_config(realm, config)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_new_realm(&self, realm: &str, config: Vec<(String, String)>) -> Result<()> {
|
||||||
|
if self.create_realm(realm)? {
|
||||||
|
if !config.is_empty() {
|
||||||
|
self.realm_set_config(realm, config)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(Error::CreateRealmFailed);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,10 +6,8 @@ edition = "2018"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libcitadel = { path = "../libcitadel" }
|
libcitadel = { path = "../libcitadel" }
|
||||||
async-io = "2.3.2"
|
zbus = "=2.0.0-beta.5"
|
||||||
blocking = "1.6.1"
|
zvariant = "2.7.0"
|
||||||
event-listener = "5.3.1"
|
|
||||||
zbus = "5.7.1"
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_repr = "0.1.20"
|
serde_repr = "0.1.8"
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
use async_io::block_on;
|
use zbus::{Connection, ObjectServer};
|
||||||
use zbus::blocking::Connection;
|
|
||||||
use zbus::object_server::SignalEmitter;
|
|
||||||
use crate::realms_manager::{RealmsManagerServer, REALMS_SERVER_OBJECT_PATH, realm_status};
|
use crate::realms_manager::{RealmsManagerServer, REALMS_SERVER_OBJECT_PATH, realm_status};
|
||||||
use libcitadel::{RealmEvent, Realm};
|
use libcitadel::{RealmEvent, Realm};
|
||||||
|
|
||||||
pub struct EventHandler {
|
pub struct EventHandler {
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
|
realms_server: RealmsManagerServer,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventHandler {
|
impl EventHandler {
|
||||||
pub fn new(connection: Connection) -> Self {
|
pub fn new(connection: Connection, realms_server: RealmsManagerServer) -> Self {
|
||||||
EventHandler { connection }
|
EventHandler { connection, realms_server }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_event(&self, ev: &RealmEvent) {
|
pub fn handle_event(&self, ev: &RealmEvent) {
|
||||||
@@ -26,49 +25,44 @@ impl EventHandler {
|
|||||||
RealmEvent::New(realm) => self.on_new(realm),
|
RealmEvent::New(realm) => self.on_new(realm),
|
||||||
RealmEvent::Removed(realm) => self.on_removed(realm),
|
RealmEvent::Removed(realm) => self.on_removed(realm),
|
||||||
RealmEvent::Current(realm) => self.on_current(realm.as_ref()),
|
RealmEvent::Current(realm) => self.on_current(realm.as_ref()),
|
||||||
RealmEvent::Starting(_) => Ok(()),
|
|
||||||
RealmEvent::Stopping(_) => Ok(()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_signal_emitter<F>(&self, func: F) -> zbus::Result<()>
|
fn with_server<F>(&self, func: F) -> zbus::Result<()>
|
||||||
where
|
where
|
||||||
F: Fn(&SignalEmitter) -> zbus::Result<()>,
|
F: Fn(&RealmsManagerServer) -> zbus::Result<()>,
|
||||||
{
|
{
|
||||||
let object_server = self.connection.object_server();
|
let mut object_server = ObjectServer::new(&self.connection);
|
||||||
let iface = object_server.interface::<_, RealmsManagerServer>(REALMS_SERVER_OBJECT_PATH)?;
|
object_server.at(REALMS_SERVER_OBJECT_PATH, self.realms_server.clone())?;
|
||||||
|
object_server.with(REALMS_SERVER_OBJECT_PATH, |iface: &RealmsManagerServer| func(iface))
|
||||||
let emitter = iface.signal_emitter();
|
|
||||||
func(emitter)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_started(&self, realm: &Realm) -> zbus::Result<()> {
|
fn on_started(&self, realm: &Realm) -> zbus::Result<()> {
|
||||||
let pid_ns = realm.pid_ns().unwrap_or(0);
|
let pid_ns = realm.pid_ns().unwrap_or(0);
|
||||||
let status = realm_status(realm);
|
let status = realm_status(realm);
|
||||||
self.with_signal_emitter(|ctx| block_on(RealmsManagerServer::realm_started(ctx, realm.name(), pid_ns, status)))
|
self.with_server(|server| server.realm_started(realm.name(), pid_ns, status))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_stopped(&self, realm: &Realm) -> zbus::Result<()> {
|
fn on_stopped(&self, realm: &Realm) -> zbus::Result<()> {
|
||||||
let status = realm_status(realm);
|
let status = realm_status(realm);
|
||||||
self.with_signal_emitter(|ctx| block_on(RealmsManagerServer::realm_stopped(ctx, realm.name(), status)))
|
self.with_server(|server| server.realm_stopped(realm.name(), status))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_new(&self, realm: &Realm) -> zbus::Result<()> {
|
fn on_new(&self, realm: &Realm) -> zbus::Result<()> {
|
||||||
let status = realm_status(realm);
|
let status = realm_status(realm);
|
||||||
let description = realm.notes().unwrap_or(String::new());
|
let description = realm.notes().unwrap_or(String::new());
|
||||||
self.with_signal_emitter(|ctx| block_on(RealmsManagerServer::realm_new(ctx, realm.name(), &description, status)))
|
self.with_server(|server| server.realm_new(realm.name(), &description, status))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_removed(&self, realm: &Realm) -> zbus::Result<()> {
|
fn on_removed(&self, realm: &Realm) -> zbus::Result<()> {
|
||||||
self.with_signal_emitter(|ctx| block_on(RealmsManagerServer::realm_removed(ctx, realm.name())))
|
self.with_server(|server| server.realm_removed(realm.name()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_current(&self, realm: Option<&Realm>) -> zbus::Result<()> {
|
fn on_current(&self, realm: Option<&Realm>) -> zbus::Result<()> {
|
||||||
self.with_signal_emitter(|ctx| {
|
self.with_server(|server| {
|
||||||
match realm {
|
match realm {
|
||||||
Some(realm) => block_on(RealmsManagerServer::realm_current(ctx, realm.name(), realm_status(realm))),
|
Some(realm) => server.realm_current(realm.name(), realm_status(realm)),
|
||||||
None => block_on(RealmsManagerServer::realm_current(ctx, "", 0)),
|
None => server.realm_current("", 0),
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user