forked from brl/citadel-tools
Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
11b3e8a016 | |||
24f786cf75 | |||
2dc8bf2922 | |||
2a16bd4c41 | |||
b7e6ee3b3c | |||
4fc3fb55db | |||
421b0e27d7 | |||
44c545a4a3 |
1320
Cargo.lock
generated
1320
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = ["citadel-realms", "citadel-installer-ui", "citadel-tool", "realmsd", "realm-config-ui" ]
|
members = ["citadel-realms", "citadel-installer-ui", "citadel-tool", "realmsd", "realm-config-ui", "launch-gnome-software" ]
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
2648
citadel-installer-ui/Cargo.lock
generated
2648
citadel-installer-ui/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -8,14 +8,14 @@ homepage = "https://subgraph.com"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libcitadel = { path = "../libcitadel" }
|
libcitadel = { path = "../libcitadel" }
|
||||||
rpassword = "4.0"
|
rpassword = "7.3"
|
||||||
clap = "2.33"
|
clap = { version = "4.5", features = ["cargo", "derive"] }
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
toml = "0.5"
|
toml = "0.8"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
byteorder = "1"
|
byteorder = "1"
|
||||||
dbus = "0.8.4"
|
dbus = "0.8.4"
|
||||||
pwhash = "0.3.1"
|
pwhash = "1.0"
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
@ -70,7 +70,7 @@ fn deploy_artifacts() -> Result<()> {
|
|||||||
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)?;
|
||||||
cmd!("/bin/mount", "-t tmpfs -o size=4g images /run/citadel/images")?;
|
cmd!("/bin/mount", "-t tmpfs -o size=5g images /run/citadel/images")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
util::read_directory("/boot/images", |dent| {
|
util::read_directory("/boot/images", |dent| {
|
||||||
|
@ -1,89 +1,97 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
use clap::{Arg,ArgMatches};
|
||||||
use clap::{App,Arg,SubCommand,ArgMatches};
|
use clap::{command, ArgAction, Command};
|
||||||
use clap::AppSettings::*;
|
|
||||||
use libcitadel::{Result, ResourceImage, Logger, LogLevel, Partition, KeyPair, ImageHeader, util};
|
|
||||||
use hex;
|
use hex;
|
||||||
|
|
||||||
pub fn main(args: Vec<String>) {
|
use libcitadel::{Result, ResourceImage, Logger, LogLevel, Partition, KeyPair, ImageHeader, util};
|
||||||
|
|
||||||
let app = App::new("citadel-image")
|
pub fn main() {
|
||||||
|
let matches = command!()
|
||||||
.about("Citadel update image builder")
|
.about("Citadel update image builder")
|
||||||
.settings(&[ArgRequiredElseHelp,ColoredHelp, DisableHelpSubcommand, DisableVersion, DeriveDisplayOrder])
|
.arg_required_else_help(true)
|
||||||
|
.disable_help_subcommand(true)
|
||||||
.subcommand(SubCommand::with_name("metainfo")
|
.subcommand(
|
||||||
|
Command::new("metainfo")
|
||||||
.about("Display metainfo variables for an image file")
|
.about("Display metainfo variables for an image file")
|
||||||
.arg(Arg::with_name("path")
|
.arg(Arg::new("path").required(true).help("Path to image file")),
|
||||||
.required(true)
|
)
|
||||||
.help("Path to image file")))
|
.subcommand(
|
||||||
|
Command::new("info")
|
||||||
.subcommand(SubCommand::with_name("info")
|
|
||||||
.about("Display metainfo variables for an image file")
|
.about("Display metainfo variables for an image file")
|
||||||
.arg(Arg::with_name("path")
|
.arg(Arg::new("path").required(true).help("Path to image file")),
|
||||||
.required(true)
|
)
|
||||||
.help("Path to image file")))
|
.subcommand(
|
||||||
|
Command::new("generate-verity")
|
||||||
.subcommand(SubCommand::with_name("generate-verity")
|
|
||||||
.about("Generate dm-verity hash tree for an image file")
|
.about("Generate dm-verity hash tree for an image file")
|
||||||
.arg(Arg::with_name("path")
|
.arg(Arg::new("path").required(true).help("Path to image file")),
|
||||||
.required(true)
|
)
|
||||||
.help("Path to image file")))
|
.subcommand(
|
||||||
|
Command::new("verify")
|
||||||
.subcommand(SubCommand::with_name("verify")
|
|
||||||
.about("Verify dm-verity hash tree for an image file")
|
.about("Verify dm-verity hash tree for an image file")
|
||||||
.arg(Arg::with_name("path")
|
.arg(
|
||||||
|
Arg::new("option")
|
||||||
|
.long("option")
|
||||||
.required(true)
|
.required(true)
|
||||||
.help("Path to image file")))
|
.help("Path to image file"),
|
||||||
|
),
|
||||||
.subcommand(SubCommand::with_name("install-rootfs")
|
)
|
||||||
|
.subcommand(
|
||||||
|
Command::new("install-rootfs")
|
||||||
.about("Install rootfs image file to a partition")
|
.about("Install rootfs image file to a partition")
|
||||||
.arg(Arg::with_name("choose")
|
.arg(
|
||||||
|
Arg::new("choose")
|
||||||
.long("just-choose")
|
.long("just-choose")
|
||||||
.help("Don't install anything, just show which partition would be chosen"))
|
.action(ArgAction::SetTrue)
|
||||||
.arg(Arg::with_name("skip-sha")
|
.help("Don't install anything, just show which partition would be chosen"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("skip-sha")
|
||||||
.long("skip-sha")
|
.long("skip-sha")
|
||||||
.help("Skip verification of header sha256 value"))
|
.action(ArgAction::SetTrue)
|
||||||
.arg(Arg::with_name("no-prefer")
|
.help("Skip verification of header sha256 value"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("no-prefer")
|
||||||
.long("no-prefer")
|
.long("no-prefer")
|
||||||
.help("Don't set PREFER_BOOT flag"))
|
.action(ArgAction::SetTrue)
|
||||||
.arg(Arg::with_name("path")
|
.help("Don't set PREFER_BOOT flag"),
|
||||||
.required_unless("choose")
|
)
|
||||||
.help("Path to image file")))
|
.arg(
|
||||||
|
Arg::new("path")
|
||||||
.subcommand(SubCommand::with_name("genkeys")
|
.required_unless_present("choose")
|
||||||
.about("Generate a pair of keys"))
|
.help("Path to image file"),
|
||||||
|
),
|
||||||
.subcommand(SubCommand::with_name("decompress")
|
)
|
||||||
|
.subcommand(Command::new("genkeys").about("Generate a pair of keys"))
|
||||||
|
.subcommand(
|
||||||
|
Command::new("decompress")
|
||||||
.about("Decompress a compressed image file")
|
.about("Decompress a compressed image file")
|
||||||
.arg(Arg::with_name("path")
|
.arg(Arg::new("path").required(true).help("Path to image file")),
|
||||||
.required(true)
|
)
|
||||||
.help("Path to image file")))
|
.subcommand(
|
||||||
|
Command::new("bless")
|
||||||
.subcommand(SubCommand::with_name("bless")
|
.about("Mark currently mounted rootfs partition as successfully booted"),
|
||||||
.about("Mark currently mounted rootfs partition as successfully booted"))
|
)
|
||||||
|
.subcommand(
|
||||||
.subcommand(SubCommand::with_name("verify-shasum")
|
Command::new("verify-shasum")
|
||||||
.about("Verify the sha256 sum of the image")
|
.about("Verify the sha256 sum of the image")
|
||||||
.arg(Arg::with_name("path")
|
.arg(Arg::new("path").required(true).help("Path to image file")),
|
||||||
.required(true)
|
)
|
||||||
.help("Path to image file")));
|
.get_matches();
|
||||||
|
|
||||||
Logger::set_log_level(LogLevel::Debug);
|
|
||||||
|
|
||||||
let matches = app.get_matches_from(args);
|
|
||||||
let result = match matches.subcommand() {
|
let result = match matches.subcommand() {
|
||||||
("metainfo", Some(m)) => metainfo(m),
|
Some(("metainfo", sub_m)) => metainfo(sub_m),
|
||||||
("info", Some(m)) => info(m),
|
Some(("info", sub_m)) => info(sub_m),
|
||||||
("generate-verity", Some(m)) => generate_verity(m),
|
Some(("generate-verity", sub_m)) => generate_verity(sub_m),
|
||||||
("verify", Some(m)) => verify(m),
|
Some(("verify", sub_m)) => verify(sub_m),
|
||||||
("sign-image", Some(m)) => sign_image(m),
|
Some(("sign-image", sub_m)) => sign_image(sub_m),
|
||||||
("genkeys", Some(_)) => genkeys(),
|
Some(("genkeys", _)) => genkeys(),
|
||||||
("decompress", Some(m)) => decompress(m),
|
Some(("decompress", sub_m)) => decompress(sub_m),
|
||||||
("verify-shasum", Some(m)) => verify_shasum(m),
|
Some(("verify-shasum", sub_m)) => verify_shasum(sub_m),
|
||||||
("install-rootfs", Some(m)) => install_rootfs(m),
|
Some(("install-rootfs", sub_m)) => install_rootfs(sub_m),
|
||||||
("install", Some(m)) => install_image(m),
|
Some(("install", sub_m)) => install_image(sub_m),
|
||||||
("bless", Some(_)) => bless(),
|
Some(("bless", _)) => bless(),
|
||||||
_ => Ok(()),
|
_ => Ok(()),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -159,7 +167,9 @@ fn verify_shasum(arg_matches: &ArgMatches) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn load_image(arg_matches: &ArgMatches) -> Result<ResourceImage> {
|
fn load_image(arg_matches: &ArgMatches) -> Result<ResourceImage> {
|
||||||
let path = arg_matches.value_of("path").expect("path argument missing");
|
let path = arg_matches.get_one::<String>("path")
|
||||||
|
.expect("path argument missing");
|
||||||
|
|
||||||
if !Path::new(path).exists() {
|
if !Path::new(path).exists() {
|
||||||
bail!("Cannot load image {}: File does not exist", path);
|
bail!("Cannot load image {}: File does not exist", path);
|
||||||
}
|
}
|
||||||
@ -171,14 +181,14 @@ fn load_image(arg_matches: &ArgMatches) -> Result<ResourceImage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn install_rootfs(arg_matches: &ArgMatches) -> Result<()> {
|
fn install_rootfs(arg_matches: &ArgMatches) -> Result<()> {
|
||||||
if arg_matches.is_present("choose") {
|
if arg_matches.get_flag("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.is_present("skip-sha") {
|
if !arg_matches.get_flag("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() {
|
||||||
@ -188,7 +198,7 @@ fn install_rootfs(arg_matches: &ArgMatches) -> Result<()> {
|
|||||||
|
|
||||||
let partition = choose_install_partition(true)?;
|
let partition = choose_install_partition(true)?;
|
||||||
|
|
||||||
if !arg_matches.is_present("no-prefer") {
|
if !arg_matches.get_flag("no-prefer") {
|
||||||
clear_prefer_boot()?;
|
clear_prefer_boot()?;
|
||||||
img.header().set_flag(ImageHeader::FLAG_PREFER_BOOT);
|
img.header().set_flag(ImageHeader::FLAG_PREFER_BOOT);
|
||||||
}
|
}
|
||||||
@ -212,7 +222,9 @@ fn sign_image(arg_matches: &ArgMatches) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn install_image(arg_matches: &ArgMatches) -> Result<()> {
|
fn install_image(arg_matches: &ArgMatches) -> Result<()> {
|
||||||
let source = arg_matches.value_of("path").expect("path argument missing");
|
let source = arg_matches.get_one::<String>("path")
|
||||||
|
.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();
|
||||||
|
@ -125,7 +125,7 @@ fn read_passphrase(prompt: &str) -> io::Result<Option<String>> {
|
|||||||
loop {
|
loop {
|
||||||
println!("{}", prompt);
|
println!("{}", prompt);
|
||||||
println!();
|
println!();
|
||||||
let passphrase = rpassword::read_password_from_tty(Some(" Passphrase : "))?;
|
let passphrase = rpassword::prompt_password(" Passphrase : ")?;
|
||||||
if passphrase.is_empty() {
|
if passphrase.is_empty() {
|
||||||
println!("Passphrase cannot be empty");
|
println!("Passphrase cannot be empty");
|
||||||
continue;
|
continue;
|
||||||
@ -133,7 +133,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::read_password_from_tty(Some(" Confirm : "))?;
|
let confirm = rpassword::prompt_password(" Confirm : ")?;
|
||||||
if confirm == "q" || confirm == "Q" {
|
if confirm == "q" || confirm == "Q" {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
@ -425,7 +425,7 @@ impl Installer {
|
|||||||
util::create_dir(&home)?;
|
util::create_dir(&home)?;
|
||||||
util::chown_user(&home)?;
|
util::chown_user(&home)?;
|
||||||
|
|
||||||
self.info("Copying /realms/skel into home diectory")?;
|
self.info("Copying /realms/skel into home directory")?;
|
||||||
util::copy_tree(&self.storage().join("realms/skel"), &home)?;
|
util::copy_tree(&self.storage().join("realms/skel"), &home)?;
|
||||||
|
|
||||||
if let Some(scheme) = Base16Scheme::by_name(MAIN_TERMINAL_SCHEME) {
|
if let Some(scheme) = Base16Scheme::by_name(MAIN_TERMINAL_SCHEME) {
|
||||||
@ -452,7 +452,7 @@ impl Installer {
|
|||||||
util::create_dir(&path)?;
|
util::create_dir(&path)?;
|
||||||
util::chown_user(&path)?;
|
util::chown_user(&path)?;
|
||||||
|
|
||||||
self.info("Copying /realms/skel into home diectory")?;
|
self.info("Copying /realms/skel into home directory")?;
|
||||||
util::copy_tree(&self.storage().join("realms/skel"), &home)?;
|
util::copy_tree(&self.storage().join("realms/skel"), &home)?;
|
||||||
|
|
||||||
self.info("Creating apt-cacher config file")?;
|
self.info("Creating apt-cacher config file")?;
|
||||||
|
@ -34,9 +34,9 @@ fn main() {
|
|||||||
} else if exe == Path::new("/usr/libexec/citadel-install-backend") {
|
} else if exe == Path::new("/usr/libexec/citadel-install-backend") {
|
||||||
install_backend::main();
|
install_backend::main();
|
||||||
} else if exe == Path::new("/usr/bin/citadel-image") {
|
} else if exe == Path::new("/usr/bin/citadel-image") {
|
||||||
image::main(args);
|
image::main();
|
||||||
} else if exe == Path::new("/usr/bin/citadel-realmfs") {
|
} else if exe == Path::new("/usr/bin/citadel-realmfs") {
|
||||||
realmfs::main(args);
|
realmfs::main();
|
||||||
} 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/libexec/citadel-desktop-sync") {
|
} else if exe == Path::new("/usr/libexec/citadel-desktop-sync") {
|
||||||
@ -57,8 +57,8 @@ fn dispatch_command(args: Vec<String>) {
|
|||||||
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(rebuild_args("citadel-image", args)),
|
"image" => image::main(),
|
||||||
"realmfs" => realmfs::main(rebuild_args("citadel-realmfs", args)),
|
"realmfs" => realmfs::main(),
|
||||||
"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)),
|
||||||
|
@ -1,28 +1,24 @@
|
|||||||
use clap::App;
|
use clap::{command, Command};
|
||||||
use clap::ArgMatches;
|
use clap::{Arg, ArgMatches};
|
||||||
|
|
||||||
use libcitadel::{Result,RealmFS,Logger,LogLevel};
|
use libcitadel::{Result,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;
|
use std::process::exit;
|
||||||
|
|
||||||
pub fn main(args: Vec<String>) {
|
pub fn main() {
|
||||||
|
|
||||||
Logger::set_log_level(LogLevel::Debug);
|
Logger::set_log_level(LogLevel::Debug);
|
||||||
|
|
||||||
let app = App::new("citadel-realmfs")
|
let matches = command!()
|
||||||
.about("Citadel realmfs image tool")
|
.about("citadel-realmfs")
|
||||||
.settings(&[ArgRequiredElseHelp,ColoredHelp, DisableHelpSubcommand, DisableVersion, DeriveDisplayOrder,SubcommandsNegateReqs])
|
.arg_required_else_help(true)
|
||||||
|
.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::with_name("image")
|
.arg(Arg::new("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::with_name("size")
|
.arg(Arg::new("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 \
|
||||||
@ -35,53 +31,53 @@ is the final absolute size of the image.")
|
|||||||
.required(true)))
|
.required(true)))
|
||||||
|
|
||||||
|
|
||||||
.subcommand(SubCommand::with_name("fork")
|
.subcommand(Command::new("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::with_name("image")
|
.arg(Arg::new("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::with_name("forkname")
|
.arg(Arg::new("forkname")
|
||||||
.help("Name of new image to create")
|
.help("Name of new image to create")
|
||||||
.required(true)))
|
.required(true)))
|
||||||
|
|
||||||
.subcommand(SubCommand::with_name("autoresize")
|
.subcommand(Command::new("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::with_name("image")
|
.arg(Arg::new("image")
|
||||||
.help("Path or name of RealmFS image")
|
.help("Path or name of RealmFS image")
|
||||||
.required(true)))
|
.required(true)))
|
||||||
|
|
||||||
.subcommand(SubCommand::with_name("update")
|
.subcommand(Command::new("update")
|
||||||
.about("Open an update shell on the image")
|
.about("Open an update shell on the image")
|
||||||
.arg(Arg::with_name("image")
|
.arg(Arg::new("image")
|
||||||
.help("Path or name of RealmFS image")
|
.help("Path or name of RealmFS image")
|
||||||
.required(true)))
|
.required(true)))
|
||||||
|
|
||||||
.subcommand(SubCommand::with_name("activate")
|
.subcommand(Command::new("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::with_name("image")
|
.arg(Arg::new("image")
|
||||||
.help("Path or name of RealmFS image to activate")
|
.help("Path or name of RealmFS image to activate")
|
||||||
.required(true)))
|
.required(true)))
|
||||||
|
|
||||||
.subcommand(SubCommand::with_name("deactivate")
|
.subcommand(Command::new("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::with_name("image")
|
.arg(Arg::new("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::with_name("image")
|
.arg(Arg::new("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() {
|
||||||
("resize", Some(m)) => resize(m),
|
Some(("resize", m)) => resize(m),
|
||||||
("autoresize", Some(m)) => autoresize(m),
|
Some(("autoresize", m)) => autoresize(m),
|
||||||
("fork", Some(m)) => fork(m),
|
Some(("fork", m)) => fork(m),
|
||||||
("update", Some(m)) => update(m),
|
Some(("update", m)) => update(m),
|
||||||
("activate", Some(m)) => activate(m),
|
Some(("activate", m)) => activate(m),
|
||||||
("deactivate", Some(m)) => deactivate(m),
|
Some(("deactivate", m)) => deactivate(m),
|
||||||
_ => image_info(&matches),
|
_ => image_info(&matches),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -92,7 +88,7 @@ is the final absolute size of the image.")
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn realmfs_image(arg_matches: &ArgMatches) -> Result<RealmFS> {
|
fn realmfs_image(arg_matches: &ArgMatches) -> Result<RealmFS> {
|
||||||
let image = match arg_matches.value_of("image") {
|
let image = match arg_matches.get_one::<String>("image") {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => bail!("Image argument required."),
|
None => bail!("Image argument required."),
|
||||||
};
|
};
|
||||||
@ -136,10 +132,9 @@ fn parse_resize_size(s: &str) -> Result<ResizeSize> {
|
|||||||
fn resize(arg_matches: &ArgMatches) -> Result<()> {
|
fn resize(arg_matches: &ArgMatches) -> Result<()> {
|
||||||
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.value_of("size") {
|
let size_arg = match arg_matches.get_one::<String>("size") {
|
||||||
Some(size) => size,
|
Some(size) => size,
|
||||||
None => "No size argument",
|
None => "No size argument",
|
||||||
|
|
||||||
};
|
};
|
||||||
info!("Size is {}", size_arg);
|
info!("Size is {}", size_arg);
|
||||||
let mode_add = size_arg.starts_with('+');
|
let mode_add = size_arg.starts_with('+');
|
||||||
@ -165,7 +160,7 @@ fn autoresize(arg_matches: &ArgMatches) -> Result<()> {
|
|||||||
|
|
||||||
fn fork(arg_matches: &ArgMatches) -> Result<()> {
|
fn fork(arg_matches: &ArgMatches) -> Result<()> {
|
||||||
let img = realmfs_image(arg_matches)?;
|
let img = realmfs_image(arg_matches)?;
|
||||||
let forkname = match arg_matches.value_of("forkname") {
|
let forkname = match arg_matches.get_one::<String>("forkname") {
|
||||||
Some(name) => name,
|
Some(name) => name,
|
||||||
None => bail!("No fork name argument"),
|
None => bail!("No fork name argument"),
|
||||||
};
|
};
|
||||||
@ -190,7 +185,7 @@ fn update(arg_matches: &ArgMatches) -> Result<()> {
|
|||||||
|
|
||||||
fn activate(arg_matches: &ArgMatches) -> Result<()> {
|
fn activate(arg_matches: &ArgMatches) -> Result<()> {
|
||||||
let img = realmfs_image(arg_matches)?;
|
let img = realmfs_image(arg_matches)?;
|
||||||
let img_arg = arg_matches.value_of("image").unwrap();
|
let img_arg = arg_matches.get_one::<String>("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);
|
||||||
@ -203,7 +198,7 @@ fn activate(arg_matches: &ArgMatches) -> Result<()> {
|
|||||||
|
|
||||||
fn deactivate(arg_matches: &ArgMatches) -> Result<()> {
|
fn deactivate(arg_matches: &ArgMatches) -> Result<()> {
|
||||||
let img = realmfs_image(arg_matches)?;
|
let img = realmfs_image(arg_matches)?;
|
||||||
let img_arg = arg_matches.value_of("image").unwrap();
|
let img_arg = arg_matches.get_one::<String>("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() {
|
||||||
|
@ -8,6 +8,7 @@ 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;
|
||||||
use crate::sync::icons::IconSync;
|
use crate::sync::icons::IconSync;
|
||||||
|
use crate::sync::REALM_BASE_PATHS;
|
||||||
|
|
||||||
/// Synchronize dot-desktop files from active realm to a target directory in Citadel.
|
/// Synchronize dot-desktop files from active realm to a target directory in Citadel.
|
||||||
pub struct DesktopFileSync {
|
pub struct DesktopFileSync {
|
||||||
@ -73,8 +74,11 @@ impl DesktopFileSync {
|
|||||||
|
|
||||||
pub fn run_sync(&mut self, clear: bool) -> Result<()> {
|
pub fn run_sync(&mut self, clear: bool) -> Result<()> {
|
||||||
|
|
||||||
self.collect_source_files("rootfs/usr/share/applications")?;
|
IconSync::ensure_theme_index_exists()?;
|
||||||
self.collect_source_files("home/.local/share/applications")?;
|
|
||||||
|
for &base_path in REALM_BASE_PATHS {
|
||||||
|
self.collect_source_files(base_path)?;
|
||||||
|
}
|
||||||
|
|
||||||
let target = Path::new(Self::CITADEL_APPLICATIONS);
|
let target = Path::new(Self::CITADEL_APPLICATIONS);
|
||||||
|
|
||||||
@ -89,12 +93,14 @@ impl DesktopFileSync {
|
|||||||
self.synchronize_items()?;
|
self.synchronize_items()?;
|
||||||
if let Some(ref icons) = self.icons {
|
if let Some(ref icons) = self.icons {
|
||||||
icons.write_known_cache()?;
|
icons.write_known_cache()?;
|
||||||
|
IconSync::update_mtime()?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_source_files(&mut self, directory: impl AsRef<Path>) -> Result<()> {
|
fn collect_source_files(&mut self, directory: impl AsRef<Path>) -> Result<()> {
|
||||||
let directory = Realms::current_realm_symlink().join(directory.as_ref());
|
let mut directory = self.realm.run_path().join(directory.as_ref());
|
||||||
|
directory.push("share/applications");
|
||||||
if directory.exists() {
|
if directory.exists() {
|
||||||
util::read_directory(&directory, |dent| {
|
util::read_directory(&directory, |dent| {
|
||||||
self.process_source_entry(dent);
|
self.process_source_entry(dent);
|
||||||
@ -120,7 +126,11 @@ impl DesktopFileSync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn remove_missing_target_files(&mut self) -> Result<()> {
|
fn remove_missing_target_files(&mut self) -> Result<()> {
|
||||||
let sources = self.source_filenames();
|
let mut 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| {
|
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() {
|
||||||
@ -176,17 +186,11 @@ impl DesktopFileSync {
|
|||||||
|
|
||||||
fn sync_item(&self, item: &DesktopItem) -> Result<()> {
|
fn sync_item(&self, item: &DesktopItem) -> Result<()> {
|
||||||
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 ")?;
|
||||||
if dfp.is_showable() {
|
// When use-flatpak is enabled a gnome-software desktop file will be generated
|
||||||
|
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))?;
|
||||||
/*
|
|
||||||
if let Some(icon_name)= dfp.icon() {
|
|
||||||
if let Some(ref icons) = self.icons {
|
|
||||||
icons.sync_icon(icon_name)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
} else {
|
} else {
|
||||||
debug!("Ignoring desktop file {} as not showable", dfp.filename());
|
debug!("Ignoring desktop file {} as not showable", dfp.filename());
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,10 @@ 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 crate::sync::desktop_file::DesktopFile;
|
use crate::sync::desktop_file::DesktopFile;
|
||||||
|
use crate::sync::REALM_BASE_PATHS;
|
||||||
|
|
||||||
pub struct IconSync {
|
pub struct IconSync {
|
||||||
realm_base: PathBuf,
|
realm: Realm,
|
||||||
cache: IconCache,
|
cache: IconCache,
|
||||||
known: RefCell<HashSet<String>>,
|
known: RefCell<HashSet<String>>,
|
||||||
known_changed: Cell<bool>,
|
known_changed: Cell<bool>,
|
||||||
@ -15,53 +16,94 @@ pub struct IconSync {
|
|||||||
|
|
||||||
impl IconSync {
|
impl IconSync {
|
||||||
const CITADEL_ICONS: &'static str = "/home/citadel/.local/share/icons";
|
const CITADEL_ICONS: &'static str = "/home/citadel/.local/share/icons";
|
||||||
|
const HICOLOR_THEME_INDEX: &'static str = "/usr/share/icons/hicolor/index.theme";
|
||||||
const KNOWN_ICONS_FILE: &'static str = "/home/citadel/.local/share/icons/known.cache";
|
const KNOWN_ICONS_FILE: &'static str = "/home/citadel/.local/share/icons/known.cache";
|
||||||
const PAPER_ICON_CACHE: &'static str = "/usr/share/icons/Paper/icon-theme.cache";
|
const PAPER_ICON_CACHE: &'static str = "/usr/share/icons/Paper/icon-theme.cache";
|
||||||
|
|
||||||
|
const HOME_PATH_PREFIX: &'static str = "/home/user/";
|
||||||
|
|
||||||
|
pub fn ensure_theme_index_exists() -> Result<()> {
|
||||||
|
let target = Path::new(Self::CITADEL_ICONS).join("hicolor").join("index.theme");
|
||||||
|
let parent_dir = target.parent().unwrap();
|
||||||
|
if !parent_dir.exists() {
|
||||||
|
util::create_dir(parent_dir)?;
|
||||||
|
}
|
||||||
|
if !target.exists() {
|
||||||
|
util::copy_file(Self::HICOLOR_THEME_INDEX, &target)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_mtime() -> Result<()> {
|
||||||
|
let target = Path::new(Self::CITADEL_ICONS).join("hicolor");
|
||||||
|
util::touch_mtime(&target)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new(realm: &Realm) -> Result<Self> {
|
pub fn new(realm: &Realm) -> Result<Self> {
|
||||||
let realm_base= realm.base_path();
|
let realm = realm.clone();
|
||||||
let cache = IconCache::open(Self::PAPER_ICON_CACHE)?;
|
let cache = IconCache::open(Self::PAPER_ICON_CACHE)?;
|
||||||
let known = Self::read_known_cache()?;
|
let known = Self::read_known_cache()?;
|
||||||
let known = RefCell::new(known);
|
let known = RefCell::new(known);
|
||||||
let known_changed = Cell::new(false);
|
let known_changed = Cell::new(false);
|
||||||
Ok(IconSync { realm_base, cache, known, known_changed })
|
Ok(IconSync { realm, cache, known, known_changed })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn realm_icon_path(&self, icon_path: &Path, prefix: &str, citadel_base_path: &Path) -> Result<PathBuf> {
|
||||||
|
let suffix = icon_path.strip_prefix(prefix)
|
||||||
|
.map_err(context!("Failed to strip prefix {} from icon path {}", prefix, icon_path.display()))?;
|
||||||
|
|
||||||
|
let base_path = citadel_base_path.canonicalize()
|
||||||
|
.map_err(context!("Failed to canonicalize base path {}", citadel_base_path.display()))?;
|
||||||
|
|
||||||
|
let joined = base_path.join(suffix);
|
||||||
|
let icon_path = joined.canonicalize()
|
||||||
|
.map_err(context!("Failed to canonicalize icon path {}", joined.display()))?;
|
||||||
|
|
||||||
|
if !icon_path.starts_with(&base_path) {
|
||||||
|
bail!("Icon path {} should start with realm base path {}", icon_path.display(), citadel_base_path.display());
|
||||||
|
}
|
||||||
|
Ok(icon_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fn sync_icon_filepath(&self, file: &mut DesktopFile, path: &Path) -> Result<()> {
|
fn sync_icon_filepath(&self, file: &mut DesktopFile, path: &Path) -> Result<()> {
|
||||||
let icon_path = path.canonicalize()
|
let icon_path = if path.starts_with(Self::HOME_PATH_PREFIX) {
|
||||||
.map_err(context!("Failed to canonicalize icon path {}", path.display()))?;
|
let realm_home = self.realm.base_path().join("home");
|
||||||
|
self.realm_icon_path(path, Self::HOME_PATH_PREFIX, &realm_home)?
|
||||||
|
} else {
|
||||||
|
let realm_root = self.realm.run_path().join("rootfs");
|
||||||
|
self.realm_icon_path(path, "/", &realm_root)?
|
||||||
|
};
|
||||||
|
|
||||||
let icon_path = icon_path.strip_prefix("/")
|
if !icon_path.is_file() {
|
||||||
.map_err(context!("Failed to strip initial / from {}", icon_path.display()))?;
|
bail!("Failed to find icon file {}", icon_path.display());
|
||||||
|
|
||||||
let realm_path = self.realm_base.join(icon_path);
|
|
||||||
|
|
||||||
if !realm_path.is_file() {
|
|
||||||
bail!("Failed to find icon file {}", realm_path.display());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let dir = Path::new(Self::CITADEL_ICONS).join("filepaths-icons");
|
let dir = Path::new(Self::CITADEL_ICONS).join("filepaths-icons");
|
||||||
let target = dir.join(realm_path.file_name()
|
let target = dir.join(icon_path.file_name()
|
||||||
.expect("Icon has no filename?"));
|
.expect("Icon has no filename?"));
|
||||||
|
|
||||||
|
if !target.exists() {
|
||||||
util::create_dir(&dir)?;
|
util::create_dir(&dir)?;
|
||||||
util::copy_file(&realm_path, &target)?;
|
util::copy_file(&icon_path, &target)?;
|
||||||
util::chmod(&target, 0o644)?;
|
util::chmod(&target, 0o644)?;
|
||||||
|
info!("Copied icon from {} to {}", icon_path.display(), target.display());
|
||||||
|
}
|
||||||
let target_str = target.display().to_string();
|
let target_str = target.display().to_string();
|
||||||
file.update_icon(&target_str);
|
file.update_icon(&target_str);
|
||||||
|
|
||||||
info!("Copied icon from {} to {}", icon_path.display(), target_str);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
||||||
}
|
}
|
||||||
pub fn sync_icon(&self, file: &mut DesktopFile, icon_name: &str) -> Result<()> {
|
pub fn sync_icon(&self, file: &mut DesktopFile, icon_name: &str) -> Result<()> {
|
||||||
|
debug!("sync_icon({})", icon_name);
|
||||||
if icon_name.starts_with("/") {
|
if icon_name.starts_with("/") {
|
||||||
return self.sync_icon_filepath(file, Path::new(icon_name));
|
return self.sync_icon_filepath(file, Path::new(icon_name));
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.is_known(icon_name) {
|
if self.is_known(icon_name) {
|
||||||
|
debug!("({}) is known", icon_name);
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
if self.cache.find_image(icon_name)? {
|
if self.cache.find_image(icon_name)? {
|
||||||
@ -70,9 +112,12 @@ impl IconSync {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.search("rootfs/usr/share/icons/hicolor", icon_name)? {
|
for &base_path in REALM_BASE_PATHS {
|
||||||
self.search("home/.local/share/icons/hicolor", icon_name)?;
|
if self.search(base_path, icon_name)? {
|
||||||
|
return Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
debug!("not found: {} ", icon_name);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,10 +153,14 @@ impl IconSync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn search(&self, subdir: impl AsRef<Path>, icon_name: &str) -> Result<bool> {
|
fn search(&self, subdir: impl AsRef<Path>, icon_name: &str) -> Result<bool> {
|
||||||
let base = self.realm_base.join(subdir.as_ref());
|
let mut base = self.realm.run_path().join(subdir.as_ref());
|
||||||
|
base.push("share/icons/hicolor");
|
||||||
if !base.exists() {
|
if !base.exists() {
|
||||||
|
debug!("Does not exist: {:?} for {}", base, icon_name);
|
||||||
|
|
||||||
return Ok(false)
|
return Ok(false)
|
||||||
}
|
}
|
||||||
|
debug!("Searching {:?} for {}", base, icon_name);
|
||||||
let mut found = false;
|
let mut found = false;
|
||||||
util::read_directory(&base, |dent| {
|
util::read_directory(&base, |dent| {
|
||||||
let apps = dent.path().join("apps");
|
let apps = dent.path().join("apps");
|
||||||
|
@ -8,20 +8,31 @@ mod icon_cache;
|
|||||||
|
|
||||||
use self::desktop_sync::DesktopFileSync;
|
use self::desktop_sync::DesktopFileSync;
|
||||||
|
|
||||||
fn has_first_arg(args: &[String], arg: &str) -> bool {
|
fn has_arg(args: &[String], arg: &str) -> bool {
|
||||||
args.len() > 1 && args[1].as_str() == arg
|
args.iter().any(|s| s.as_str() == arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const REALM_BASE_PATHS:&[&str] = &[
|
||||||
|
"rootfs/usr",
|
||||||
|
"flatpak/exports",
|
||||||
|
"home/.local",
|
||||||
|
"home/.local/share/flatpak/exports"
|
||||||
|
];
|
||||||
|
|
||||||
pub fn main(args: Vec<String>) {
|
pub fn main(args: Vec<String>) {
|
||||||
|
|
||||||
|
if has_arg(&args, "-v") {
|
||||||
Logger::set_log_level(LogLevel::Debug);
|
Logger::set_log_level(LogLevel::Debug);
|
||||||
|
} else {
|
||||||
|
Logger::set_log_level(LogLevel::Info);
|
||||||
|
}
|
||||||
|
|
||||||
if has_first_arg(&args, "--all") {
|
if has_arg(&args, "--all") {
|
||||||
if let Err(e) = DesktopFileSync::sync_active_realms() {
|
if let Err(e) = DesktopFileSync::sync_active_realms() {
|
||||||
println!("Sync all active realms failed: {}", e);
|
println!("Sync all active realms failed: {}", e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let clear = has_first_arg(&args, "--clear");
|
let clear = has_arg(&args, "--clear");
|
||||||
if let Err(e) = sync(clear) {
|
if let Err(e) = sync(clear) {
|
||||||
println!("Desktop file sync failed: {}", e);
|
println!("Desktop file sync failed: {}", e);
|
||||||
}
|
}
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
|
|
||||||
### Old Sync
|
|
||||||
|
|
||||||
#### citadel-current-watcher.path
|
|
||||||
|
|
||||||
[Path]
|
|
||||||
PathChanged=/run/citadel/realms/current
|
|
||||||
|
|
||||||
#### citadel-current-watcher.service
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=oneshot
|
|
||||||
ExecStart=/usr/libexec/citadel-desktop-sync --clear
|
|
||||||
ExecStart=/usr/bin/systemctl restart citadel-desktop-watcher.path
|
|
||||||
|
|
||||||
#### citadel-desktop-watcher.path
|
|
||||||
|
|
||||||
[Path]
|
|
||||||
PathChanged=/run/citadel/realms/current/current.realm/rootfs/usr/share/applications
|
|
||||||
PathChanged=/run/citadel/realms/current/current.realm/home/.local/share/applications
|
|
||||||
|
|
||||||
#### citadel-desktop-watcher.service
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=oneshot
|
|
||||||
ExecStart=/usr/libexec/citadel-desktop-sync
|
|
||||||
|
|
||||||
### New Sync
|
|
||||||
|
|
||||||
* Added a new command line option `--all` for syncronizing all active realms
|
|
@ -9,10 +9,10 @@ lazy_static! {
|
|||||||
static ref KEY_WHITELIST: HashSet<&'static str> = [
|
static ref KEY_WHITELIST: HashSet<&'static str> = [
|
||||||
"Type", "Version", "Name", "GenericName", "NoDisplay", "Comment", "Icon", "Hidden",
|
"Type", "Version", "Name", "GenericName", "NoDisplay", "Comment", "Icon", "Hidden",
|
||||||
"OnlyShowIn", "NotShowIn", "Path", "Terminal", "Actions", "MimeType",
|
"OnlyShowIn", "NotShowIn", "Path", "Terminal", "Actions", "MimeType",
|
||||||
"Categories", "Keywords", "StartupNotify", "StartupWMClass", "URL", "DocPath",
|
"Categories", "Keywords", "StartupNotify", "StartupWMClass", "URL", "DocPath", "SingleMainWindow",
|
||||||
"X-GNOME-FullName", "X-GNOME-Provides", "X-Desktop-File-Install-Version", "X-GNOME-UsesNotifications",
|
"X-GNOME-FullName", "X-GNOME-Provides", "X-Desktop-File-Install-Version", "X-GNOME-UsesNotifications",
|
||||||
"X-GNOME-DocPath", "X-Geoclue-Reason", "X-GNOME-SingleWindow", "X-GNOME-Gettext-Domain",
|
"X-GNOME-DocPath", "X-Geoclue-Reason", "X-GNOME-SingleWindow", "X-GNOME-Gettext-Domain",
|
||||||
"X-MultipleArgs",
|
"X-MultipleArgs", "X-Flatpak", "X-Flatpak-Tags", "X-SingleMainWindow",
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
|
|
||||||
// These are keys which are recognized but deliberately ignored.
|
// These are keys which are recognized but deliberately ignored.
|
||||||
@ -23,7 +23,7 @@ lazy_static! {
|
|||||||
"X-GNOME-Bugzilla-ExtraInfoScript", "X-GNOME-Bugzilla-OtherBinaries", "X-GNOME-Autostart-enabled",
|
"X-GNOME-Bugzilla-ExtraInfoScript", "X-GNOME-Bugzilla-OtherBinaries", "X-GNOME-Autostart-enabled",
|
||||||
"X-AppInstall-Package", "X-KDE-SubstituteUID", "X-Ubuntu-Gettext-Domain", "X-AppInstall-Keywords",
|
"X-AppInstall-Package", "X-KDE-SubstituteUID", "X-Ubuntu-Gettext-Domain", "X-AppInstall-Keywords",
|
||||||
"X-Ayatana-Desktop-Shortcuts", "X-GNOME-Settings-Panel", "X-GNOME-WMSettingsModule", "X-GNOME-WMName",
|
"X-Ayatana-Desktop-Shortcuts", "X-GNOME-Settings-Panel", "X-GNOME-WMSettingsModule", "X-GNOME-WMName",
|
||||||
"X-GnomeWMSettingsLibrary",
|
"X-GnomeWMSettingsLibrary", "X-Purism-FormFactor", "X-ExecArg",
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<schemalist>
|
<schemalist>
|
||||||
<schema id="com.subgraph.citadel" path="/com/subgraph/citadel/">
|
<schema id="com.subgraph.citadel" path="/com/subgraph/citadel/">
|
||||||
<key name="frame-color-list" type="as">
|
<key name="label-color-list" type="as">
|
||||||
<default>[
|
<default>[
|
||||||
'rgb(153,193,241)',
|
'rgb(153,193,241)',
|
||||||
'rgb(143,240,164)',
|
'rgb(143,240,164)',
|
||||||
@ -14,7 +14,7 @@
|
|||||||
<summary />
|
<summary />
|
||||||
</key>
|
</key>
|
||||||
|
|
||||||
<key name="realm-frame-colors" type="as">
|
<key name="realm-label-colors" type="as">
|
||||||
<default>['main:rgb(153,193,241)']</default>
|
<default>['main:rgb(153,193,241)']</default>
|
||||||
</key>
|
</key>
|
||||||
|
|
||||||
|
@ -7,10 +7,12 @@
|
|||||||
<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"
|
||||||
|
8
launch-gnome-software/Cargo.toml
Normal file
8
launch-gnome-software/Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "launch-gnome-software"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
libcitadel = { path = "../libcitadel" }
|
||||||
|
anyhow = "1.0"
|
67
launch-gnome-software/src/main.rs
Normal file
67
launch-gnome-software/src/main.rs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
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,6 +10,7 @@ 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"
|
||||||
|
221
libcitadel/src/flatpak/bubblewrap.rs
Normal file
221
libcitadel/src/flatpak/bubblewrap.rs
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
259
libcitadel/src/flatpak/launcher.rs
Normal file
259
libcitadel/src/flatpak/launcher.rs
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
16
libcitadel/src/flatpak/mod.rs
Normal file
16
libcitadel/src/flatpak/mod.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
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";
|
132
libcitadel/src/flatpak/netns.rs
Normal file
132
libcitadel/src/flatpak/netns.rs
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
58
libcitadel/src/flatpak/setup.rs
Normal file
58
libcitadel/src/flatpak/setup.rs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
144
libcitadel/src/flatpak/status.rs
Normal file
144
libcitadel/src/flatpak/status.rs
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
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()))
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,8 @@ mod realm;
|
|||||||
pub mod terminal;
|
pub mod terminal;
|
||||||
mod system;
|
mod system;
|
||||||
|
|
||||||
|
pub mod flatpak;
|
||||||
|
|
||||||
pub use crate::config::OsRelease;
|
pub use crate::config::OsRelease;
|
||||||
pub use crate::blockdev::BlockDev;
|
pub use crate::blockdev::BlockDev;
|
||||||
pub use crate::cmdline::CommandLine;
|
pub use crate::cmdline::CommandLine;
|
||||||
|
@ -62,6 +62,11 @@ 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;
|
||||||
|
@ -77,6 +77,9 @@ 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>,
|
||||||
|
|
||||||
@ -201,6 +204,7 @@ 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),
|
||||||
@ -233,6 +237,7 @@ 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,
|
||||||
@ -267,6 +272,14 @@ impl RealmConfig {
|
|||||||
self.bool_value(|c| c.use_fuse)
|
self.bool_value(|c| c.use_fuse)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// 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.
|
||||||
|
@ -12,7 +12,9 @@ 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),
|
||||||
@ -22,7 +24,9 @@ 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()),
|
||||||
|
@ -60,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() {
|
if config.fuse() || config.flatpak() {
|
||||||
self.add_device("/dev/fuse");
|
self.add_device("/dev/fuse");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,6 +153,10 @@ 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)?;
|
||||||
|
@ -4,6 +4,8 @@ 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;
|
||||||
|
|
||||||
@ -21,6 +23,7 @@ struct Inner {
|
|||||||
events: RealmEventListener,
|
events: RealmEventListener,
|
||||||
realms: Realms,
|
realms: Realms,
|
||||||
realmfs_set: RealmFSSet,
|
realmfs_set: RealmFSSet,
|
||||||
|
pid_mapper: PidMapper,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Inner {
|
impl Inner {
|
||||||
@ -28,7 +31,8 @@ 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()?;
|
||||||
Ok(Inner { events, realms, realmfs_set })
|
let pid_mapper = PidMapper::new()?;
|
||||||
|
Ok(Inner { events, realms, realmfs_set, pid_mapper })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +194,13 @@ impl RealmManager {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
info!("Starting realm {}", realm.name());
|
info!("Starting realm {}", realm.name());
|
||||||
self._start_realm(realm, &mut HashSet::new())?;
|
self.inner().events.send_event(RealmEvent::Starting(realm.clone()));
|
||||||
|
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)
|
||||||
@ -230,6 +240,10 @@ 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)?;
|
||||||
@ -268,6 +282,15 @@ 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());
|
||||||
@ -275,10 +298,21 @@ 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);
|
||||||
self.systemd.stop_realm(realm)?;
|
if let Err(err) = 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();
|
||||||
@ -335,8 +369,8 @@ impl RealmManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn realm_by_pid(&self, pid: u32) -> PidLookupResult {
|
pub fn realm_by_pid(&self, pid: u32) -> PidLookupResult {
|
||||||
let mapper = PidMapper::new(self.active_realms(false));
|
let realms = self.realm_list();
|
||||||
mapper.lookup_pid(pid as libc::pid_t)
|
self.inner_mut().pid_mapper.lookup_pid(pid as libc::pid_t, realms)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rescan_realms(&self) -> Result<(Vec<Realm>,Vec<Realm>)> {
|
pub fn rescan_realms(&self) -> Result<(Vec<Realm>,Vec<Realm>)> {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use procfs::process::Process;
|
use procfs::process::Process;
|
||||||
use crate::Realm;
|
use crate::{Result, Realm};
|
||||||
|
use crate::flatpak::{SANDBOX_STATUS_FILE_DIRECTORY, SandboxStatus};
|
||||||
|
|
||||||
pub enum PidLookupResult {
|
pub enum PidLookupResult {
|
||||||
Unknown,
|
Unknown,
|
||||||
@ -9,14 +10,15 @@ pub enum PidLookupResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct PidMapper {
|
pub struct PidMapper {
|
||||||
active_realms: Vec<Realm>,
|
sandbox_status: SandboxStatus,
|
||||||
my_pid_ns_id: Option<u64>,
|
my_pid_ns_id: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PidMapper {
|
impl PidMapper {
|
||||||
pub fn new(active_realms: Vec<Realm>) -> Self {
|
pub fn new() -> Result<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();
|
||||||
PidMapper { active_realms, my_pid_ns_id }
|
Ok(PidMapper { sandbox_status, my_pid_ns_id })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_process(pid: libc::pid_t) -> Option<Process> {
|
fn read_process(pid: libc::pid_t) -> Option<Process> {
|
||||||
@ -72,7 +74,30 @@ impl PidMapper {
|
|||||||
Self::read_process(ppid)
|
Self::read_process(ppid)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lookup_pid(&self, pid: libc::pid_t) -> PidLookupResult {
|
fn refresh_sandbox_status(&mut self) -> Result<()> {
|
||||||
|
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;
|
||||||
|
|
||||||
@ -92,13 +117,17 @@ impl PidMapper {
|
|||||||
return PidLookupResult::Citadel;
|
return PidLookupResult::Citadel;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(realm) = self.active_realms.iter()
|
if let Some(realm) = realms.iter()
|
||||||
.find(|r| r.has_pid_ns(pid_ns_id))
|
.find(|r| r.is_active() && 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,
|
||||||
@ -108,5 +137,4 @@ impl PidMapper {
|
|||||||
}
|
}
|
||||||
PidLookupResult::Unknown
|
PidLookupResult::Unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -169,6 +169,20 @@ 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()
|
||||||
}
|
}
|
||||||
@ -279,6 +293,9 @@ 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)
|
||||||
}
|
}
|
||||||
@ -300,6 +317,9 @@ 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);
|
||||||
|
@ -31,6 +31,28 @@ 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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 needs to be resized to a larger size, returns the
|
/// If the RealmFS has less than `AUTO_RESIZE_MINIMUM_FREE` blocks free then choose a new
|
||||||
/// recommended size.
|
/// size to resize the filesystem to and return it. Otherwise, return `None`
|
||||||
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,22 +56,37 @@ 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() {
|
||||||
let metainfo_nblocks = realmfs.metainfo().nblocks() + 1;
|
return None;
|
||||||
let increase_multiple = metainfo_nblocks / AUTO_RESIZE_INCREASE_SIZE.nblocks();
|
}
|
||||||
let grow_size = (increase_multiple + 1) * AUTO_RESIZE_INCREASE_SIZE.nblocks();
|
|
||||||
let mask = grow_size - 1;
|
let metainfo_nblocks = realmfs.metainfo().nblocks();
|
||||||
let grow_blocks = (free_blocks + mask) & !mask;
|
|
||||||
Some(ResizeSize::blocks(grow_blocks))
|
if metainfo_nblocks >= AUTO_RESIZE_INCREASE_SIZE.nblocks() {
|
||||||
|
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 {
|
||||||
None
|
// Otherwise for original size under 4GB, since raising to 4GB is not enough,
|
||||||
|
// 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 {
|
||||||
|
@ -76,8 +76,6 @@ impl <'a> Update<'a> {
|
|||||||
}
|
}
|
||||||
self.realmfs.copy_image_file(self.target())?;
|
self.realmfs.copy_image_file(self.target())?;
|
||||||
|
|
||||||
self.truncate_verity()?;
|
|
||||||
self.resize_image_file()?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,9 +113,8 @@ impl <'a> Update<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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() + 1
|
self.realmfs.metainfo().nblocks()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unmount_update_image(&mut self) {
|
fn unmount_update_image(&mut self) {
|
||||||
@ -159,7 +156,8 @@ impl <'a> Update<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn set_target_len(&self, nblocks: usize) -> Result<()> {
|
fn set_target_len(&self, nblocks: usize) -> Result<()> {
|
||||||
let len = (nblocks * BLOCK_SIZE) as u64;
|
// add one block for header block
|
||||||
|
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)
|
||||||
@ -176,7 +174,7 @@ impl <'a> Update<'a> {
|
|||||||
|
|
||||||
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 {
|
} else if file_nblocks > (metainfo_nblocks + 1) {
|
||||||
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(())
|
||||||
@ -185,7 +183,7 @@ impl <'a> Update<'a> {
|
|||||||
// 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() + 1,
|
Some(rs) => rs.nblocks(),
|
||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -224,7 +222,7 @@ impl <'a> Update<'a> {
|
|||||||
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() - 1,
|
None => self.metainfo_nblock_size(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let salt = hex::encode(randombytes(32));
|
let salt = hex::encode(randombytes(32));
|
||||||
@ -232,20 +230,11 @@ impl <'a> Update<'a> {
|
|||||||
.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");
|
||||||
|
@ -7,6 +7,8 @@ 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 walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
use libc;
|
use libc;
|
||||||
@ -216,7 +218,8 @@ 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();
|
||||||
if path.exists() {
|
let is_symlink = fs::symlink_metadata(path).is_ok();
|
||||||
|
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))?;
|
||||||
}
|
}
|
||||||
@ -334,3 +337,71 @@ pub fn is_euid_root() -> bool {
|
|||||||
libc::geteuid() == 0
|
libc::geteuid() == 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn utimes(path: &Path, atime: i64, mtime: i64) -> Result<()> {
|
||||||
|
let cstr = CString::new(path.as_os_str().as_bytes())
|
||||||
|
.expect("path contains null byte");
|
||||||
|
|
||||||
|
let atimeval = libc::timeval {
|
||||||
|
tv_sec: atime,
|
||||||
|
tv_usec: 0,
|
||||||
|
};
|
||||||
|
let mtimeval = libc::timeval {
|
||||||
|
tv_sec: mtime,
|
||||||
|
tv_usec: 0,
|
||||||
|
};
|
||||||
|
let times = [atimeval,mtimeval];
|
||||||
|
let ret = unsafe { libc::utimes(cstr.as_ptr(), times.as_ptr()) };
|
||||||
|
if ret != 0 {
|
||||||
|
bail!("Failed to call utimes: {:?}", io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn touch_mtime(path: &Path) -> Result<()> {
|
||||||
|
let meta = path.metadata()
|
||||||
|
.map_err(context!("failed to retrieve metadata from {:?}", path))?;
|
||||||
|
let now = SystemTime::now().duration_since(UNIX_EPOCH)
|
||||||
|
.map_err(context!("Could not get system time as UNIX_EPOCH"))?;
|
||||||
|
|
||||||
|
let mtime = now.as_secs() as i64;
|
||||||
|
|
||||||
|
utimes(path, meta.atime(),mtime)?;
|
||||||
|
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(())
|
||||||
|
}
|
@ -9,7 +9,7 @@ homepage = "https://subgraph.com"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
libcitadel = { path = "../libcitadel" }
|
libcitadel = { path = "../libcitadel" }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
zvariant = "2.7.0"
|
zvariant = "4.2.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
zbus = "=2.0.0-beta.5"
|
zbus = "4.4.0"
|
||||||
gtk = { version = "0.14.0", features = ["v3_24"] }
|
gtk = { version = "0.14.0", features = ["v3_24"] }
|
||||||
|
@ -68,18 +68,18 @@ impl CitadelSettings {
|
|||||||
|
|
||||||
let list = self.realms.iter().map(|r| r.to_string()).collect::<Vec<String>>();
|
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>>();
|
let realms = list.iter().map(|s| s.as_str()).collect::<Vec<&str>>();
|
||||||
self.settings.set_strv("realm-frame-colors", &realms).is_ok()
|
self.settings.set_strv("realm-label-colors", &realms).is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let settings = gio::Settings::new("com.subgraph.citadel");
|
let settings = gio::Settings::new("com.subgraph.citadel");
|
||||||
|
|
||||||
let realms = settings.strv("realm-frame-colors")
|
let realms = settings.strv("realm-label-colors")
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|gs| RealmFrameColor::try_from(gs.as_str()).ok())
|
.flat_map(|gs| RealmFrameColor::try_from(gs.as_str()).ok())
|
||||||
.collect::<Vec<RealmFrameColor>>();
|
.collect::<Vec<RealmFrameColor>>();
|
||||||
|
|
||||||
let frame_colors = settings.strv("frame-color-list").into_iter()
|
let frame_colors = settings.strv("label-color-list").into_iter()
|
||||||
.flat_map(|gs| gs.as_str().parse().ok())
|
.flat_map(|gs| gs.as_str().parse().ok())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use zbus::dbus_proxy;
|
use zvariant::Type;
|
||||||
use zvariant::derive::Type;
|
|
||||||
use serde::{Serialize,Deserialize};
|
use serde::{Serialize,Deserialize};
|
||||||
|
use zbus::proxy;
|
||||||
|
use zbus::blocking::Connection;
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
|
|
||||||
#[derive(Deserialize,Serialize,Type)]
|
#[derive(Deserialize,Serialize,Type)]
|
||||||
@ -85,10 +85,12 @@ impl RealmConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[dbus_proxy(
|
#[proxy(
|
||||||
default_service = "com.subgraph.realms",
|
default_service = "com.subgraph.realms",
|
||||||
interface = "com.subgraph.realms.Manager",
|
interface = "com.subgraph.realms.Manager",
|
||||||
default_path = "/com/subgraph/realms"
|
default_path = "/com/subgraph/realms",
|
||||||
|
gen_blocking = true,
|
||||||
|
gen_async = false,
|
||||||
)]
|
)]
|
||||||
pub trait RealmsManager {
|
pub trait RealmsManager {
|
||||||
fn get_current(&self) -> zbus::Result<String>;
|
fn get_current(&self) -> zbus::Result<String>;
|
||||||
@ -102,7 +104,7 @@ pub trait RealmsManager {
|
|||||||
|
|
||||||
impl RealmsManagerProxy<'_> {
|
impl RealmsManagerProxy<'_> {
|
||||||
pub fn connect() -> Result<Self> {
|
pub fn connect() -> Result<Self> {
|
||||||
let connection = zbus::Connection::new_system()?;
|
let connection = Connection::system()?;
|
||||||
|
|
||||||
let proxy = RealmsManagerProxy::new(&connection)
|
let proxy = RealmsManagerProxy::new(&connection)
|
||||||
.map_err(|_| Error::ManagerConnect)?;
|
.map_err(|_| Error::ManagerConnect)?;
|
||||||
|
@ -6,8 +6,11 @@ edition = "2018"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libcitadel = { path = "../libcitadel" }
|
libcitadel = { path = "../libcitadel" }
|
||||||
zbus = "=2.0.0-beta.5"
|
async-io = "2.3.2"
|
||||||
zvariant = "2.7.0"
|
blocking = "1.6.1"
|
||||||
|
event-listener = "5.3.1"
|
||||||
|
zbus = "4.4.0"
|
||||||
|
zvariant = "4.2.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_repr = "0.1.8"
|
serde_repr = "0.1.8"
|
||||||
|
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
use zbus::{Connection, ObjectServer};
|
use async_io::block_on;
|
||||||
|
use zbus::blocking::Connection;
|
||||||
|
use zbus::SignalContext;
|
||||||
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, realms_server: RealmsManagerServer) -> Self {
|
pub fn new(connection: Connection) -> Self {
|
||||||
EventHandler { connection, realms_server }
|
EventHandler { connection }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_event(&self, ev: &RealmEvent) {
|
pub fn handle_event(&self, ev: &RealmEvent) {
|
||||||
@ -25,44 +26,49 @@ 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_server<F>(&self, func: F) -> zbus::Result<()>
|
fn with_signal_context<F>(&self, func: F) -> zbus::Result<()>
|
||||||
where
|
where
|
||||||
F: Fn(&RealmsManagerServer) -> zbus::Result<()>,
|
F: Fn(&SignalContext) -> zbus::Result<()>,
|
||||||
{
|
{
|
||||||
let mut object_server = ObjectServer::new(&self.connection);
|
let object_server = self.connection.object_server();
|
||||||
object_server.at(REALMS_SERVER_OBJECT_PATH, self.realms_server.clone())?;
|
let iface = object_server.interface::<_, RealmsManagerServer>(REALMS_SERVER_OBJECT_PATH)?;
|
||||||
object_server.with(REALMS_SERVER_OBJECT_PATH, |iface: &RealmsManagerServer| func(iface))
|
|
||||||
|
let ctx = iface.signal_context();
|
||||||
|
func(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
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_server(|server| server.realm_started(realm.name(), pid_ns, status))
|
self.with_signal_context(|ctx| block_on(RealmsManagerServer::realm_started(ctx, 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_server(|server| server.realm_stopped(realm.name(), status))
|
self.with_signal_context(|ctx| block_on(RealmsManagerServer::realm_stopped(ctx, 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_server(|server| server.realm_new(realm.name(), &description, status))
|
self.with_signal_context(|ctx| block_on(RealmsManagerServer::realm_new(ctx, realm.name(), &description, status)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_removed(&self, realm: &Realm) -> zbus::Result<()> {
|
fn on_removed(&self, realm: &Realm) -> zbus::Result<()> {
|
||||||
self.with_server(|server| server.realm_removed(realm.name()))
|
self.with_signal_context(|ctx| block_on(RealmsManagerServer::realm_removed(ctx, realm.name())))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_current(&self, realm: Option<&Realm>) -> zbus::Result<()> {
|
fn on_current(&self, realm: Option<&Realm>) -> zbus::Result<()> {
|
||||||
self.with_server(|server| {
|
self.with_signal_context(|ctx| {
|
||||||
match realm {
|
match realm {
|
||||||
Some(realm) => server.realm_current(realm.name(), realm_status(realm)),
|
Some(realm) => block_on(RealmsManagerServer::realm_current(ctx, realm.name(), realm_status(realm))),
|
||||||
None => server.realm_current("", 0),
|
None => block_on(RealmsManagerServer::realm_current(ctx, "", 0)),
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
#[macro_use] extern crate libcitadel;
|
#[macro_use] extern crate libcitadel;
|
||||||
|
|
||||||
use zbus::{Connection, fdo};
|
use std::env;
|
||||||
|
use std::sync::Arc;
|
||||||
use libcitadel::{Logger, LogLevel, Result};
|
use event_listener::{Event, Listener};
|
||||||
|
use zbus::blocking::Connection;
|
||||||
use crate::realms_manager::RealmsManagerServer;
|
use zbus::fdo::ObjectManager;
|
||||||
|
use libcitadel::{Logger, LogLevel, Result, RealmManager};
|
||||||
|
use crate::next::{RealmsManagerServer2, REALMS2_SERVER_OBJECT_PATH};
|
||||||
|
use crate::realms_manager::{RealmsManagerServer, REALMS_SERVER_OBJECT_PATH};
|
||||||
|
|
||||||
mod realms_manager;
|
mod realms_manager;
|
||||||
mod events;
|
mod events;
|
||||||
|
|
||||||
|
mod next;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
if let Err(e) = run_realm_manager() {
|
if let Err(e) = run_realm_manager() {
|
||||||
@ -16,24 +20,43 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_system_connection() -> zbus::Result<Connection> {
|
fn register_realms_manager_server(connection: &Connection, realm_manager: &Arc<RealmManager>, quit_event: &Arc<Event>) -> Result<()> {
|
||||||
let connection = zbus::Connection::new_system()?;
|
let server = RealmsManagerServer::load(&connection, realm_manager.clone(), quit_event.clone())
|
||||||
fdo::DBusProxy::new(&connection)?.request_name("com.subgraph.realms", fdo::RequestNameFlags::AllowReplacement.into())?;
|
.map_err(context!("Loading realms server"))?;
|
||||||
Ok(connection)
|
connection.object_server().at(REALMS_SERVER_OBJECT_PATH, server).map_err(context!("registering realms manager object"))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_realms2_manager_server(connection: &Connection, realm_manager: &Arc<RealmManager>, quit_event: &Arc<Event>) -> Result<()> {
|
||||||
|
let server2 = RealmsManagerServer2::load(&connection, realm_manager.clone(), quit_event.clone())
|
||||||
|
.map_err(context!("Loading realms2 server"))?;
|
||||||
|
connection.object_server().at(REALMS2_SERVER_OBJECT_PATH, server2).map_err(context!("registering realms manager object"))?;
|
||||||
|
connection.object_server().at(REALMS2_SERVER_OBJECT_PATH, ObjectManager).map_err(context!("registering ObjectManager"))?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_realm_manager() -> Result<()> {
|
fn run_realm_manager() -> Result<()> {
|
||||||
Logger::set_log_level(LogLevel::Verbose);
|
Logger::set_log_level(LogLevel::Verbose);
|
||||||
|
|
||||||
let connection = create_system_connection()
|
let testing = env::args().skip(1).any(|s| s == "--testing");
|
||||||
|
|
||||||
|
let connection = Connection::system()
|
||||||
.map_err(context!("ZBus Connection error"))?;
|
.map_err(context!("ZBus Connection error"))?;
|
||||||
|
|
||||||
let mut object_server = RealmsManagerServer::register(&connection)?;
|
|
||||||
|
|
||||||
loop {
|
let realm_manager = RealmManager::load()?;
|
||||||
if let Err(err) = object_server.try_handle_next() {
|
let quit_event = Arc::new(Event::new());
|
||||||
warn!("Error handling DBus message: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if testing {
|
||||||
|
register_realms2_manager_server(&connection, &realm_manager, &quit_event)?;
|
||||||
|
connection.request_name("com.subgraph.Realms2")
|
||||||
|
.map_err(context!("acquiring realms manager name"))?;
|
||||||
|
} else {
|
||||||
|
register_realms_manager_server(&connection, &realm_manager, &quit_event)?;
|
||||||
|
register_realms2_manager_server(&connection, &realm_manager, &quit_event)?;
|
||||||
|
connection.request_name("com.subgraph.realms")
|
||||||
|
.map_err(context!("acquiring realms manager name"))?;
|
||||||
|
};
|
||||||
|
quit_event.listen().wait();
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
201
realmsd/src/next/config.rs
Normal file
201
realmsd/src/next/config.rs
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use zbus::fdo;
|
||||||
|
use zvariant::Type;
|
||||||
|
use libcitadel::{OverlayType, Realm, GLOBAL_CONFIG};
|
||||||
|
use libcitadel::terminal::Base16Scheme;
|
||||||
|
use crate::next::manager::failed;
|
||||||
|
|
||||||
|
const BOOL_CONFIG_VARS: &[&str] = &[
|
||||||
|
"use-gpu", "use-wayland", "use-x11", "use-sound",
|
||||||
|
"use-shared-dir", "use-network", "use-kvm", "use-ephemeral-home",
|
||||||
|
"use-media-dir", "use-fuse", "use-flatpak", "use-gpu-card0"
|
||||||
|
];
|
||||||
|
|
||||||
|
fn is_bool_config_variable(variable: &str) -> bool {
|
||||||
|
BOOL_CONFIG_VARS.iter().any(|&s| s == variable)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize,Serialize,Type)]
|
||||||
|
pub struct RealmConfigVars {
|
||||||
|
items: HashMap<String,String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RealmConfigVars {
|
||||||
|
fn new() -> Self {
|
||||||
|
RealmConfigVars { items: HashMap::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_global() -> Self {
|
||||||
|
Self::new_from_config(&GLOBAL_CONFIG)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_from_realm(realm: &Realm) -> Self {
|
||||||
|
let config = realm.config();
|
||||||
|
Self::new_from_config(&config)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_from_config(config: &libcitadel::RealmConfig) -> Self {
|
||||||
|
let mut vars = RealmConfigVars::new();
|
||||||
|
vars.add_bool("use-gpu", config.gpu());
|
||||||
|
vars.add_bool("use-gpu-card0", config.gpu_card0());
|
||||||
|
vars.add_bool("use-wayland", config.wayland());
|
||||||
|
vars.add_bool("use-x11", config.x11());
|
||||||
|
vars.add_bool("use-sound", config.sound());
|
||||||
|
vars.add_bool("use-shared-dir", config.shared_dir());
|
||||||
|
vars.add_bool("use-network", config.network());
|
||||||
|
vars.add_bool("use-kvm", config.kvm());
|
||||||
|
vars.add_bool("use-ephemeral-home", config.ephemeral_home());
|
||||||
|
vars.add_bool("use-media-dir", config.media_dir());
|
||||||
|
vars.add_bool("use-fuse", config.fuse());
|
||||||
|
vars.add_bool("use-flatpak", config.flatpak());
|
||||||
|
|
||||||
|
let overlay = match config.overlay() {
|
||||||
|
OverlayType::None => "none",
|
||||||
|
OverlayType::TmpFS => "tmpfs",
|
||||||
|
OverlayType::Storage => "storage",
|
||||||
|
};
|
||||||
|
vars.add("overlay", overlay);
|
||||||
|
|
||||||
|
let scheme = match config.terminal_scheme() {
|
||||||
|
Some(name) => name.to_string(),
|
||||||
|
None => String::new(),
|
||||||
|
};
|
||||||
|
vars.add("terminal-scheme", scheme);
|
||||||
|
vars.add("realmfs", config.realmfs());
|
||||||
|
vars
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_bool(&mut self, name: &str, val: bool) {
|
||||||
|
let valstr = if val { "true".to_string() } else { "false".to_string() };
|
||||||
|
self.add(name, valstr);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add<S,T>(&mut self, k: S, v: T) where S: Into<String>, T: Into<String> {
|
||||||
|
self.items.insert(k.into(), v.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct RealmConfig {
|
||||||
|
realm: Realm,
|
||||||
|
changed: Arc<AtomicBool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RealmConfig {
|
||||||
|
pub fn new(realm: Realm) -> Self {
|
||||||
|
let changed = Arc::new(AtomicBool::new(false));
|
||||||
|
RealmConfig { realm, changed }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mark_changed(&self) {
|
||||||
|
self.changed.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_changed(&self) -> bool {
|
||||||
|
self.changed.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn config_vars(&self) -> RealmConfigVars {
|
||||||
|
RealmConfigVars::new_from_realm(&self.realm)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_bool_var(&mut self, var: &str, value: &str) -> fdo::Result<()> {
|
||||||
|
let v = match value {
|
||||||
|
"true" => true,
|
||||||
|
"false" => false,
|
||||||
|
_ => return failed(format!("Invalid boolean value '{}' for realm config variable '{}'", value, var)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut has_changed = true;
|
||||||
|
self.realm.with_mut_config(|c| {
|
||||||
|
match var {
|
||||||
|
"use-gpu" if c.gpu() != v => c.use_gpu = Some(v),
|
||||||
|
_ => has_changed = false,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if has_changed {
|
||||||
|
self.mark_changed();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_overlay(&mut self, value: &str) -> fdo::Result<()> {
|
||||||
|
let val = match value {
|
||||||
|
"tmpfs" => Some("tmpfs".to_string()),
|
||||||
|
"storage" => Some("storage".to_string()),
|
||||||
|
"none" => None,
|
||||||
|
_ => return failed(format!("Invalid value '{}' for overlay config", value)),
|
||||||
|
};
|
||||||
|
if self.realm.config().overlay != val {
|
||||||
|
self.realm.with_mut_config(|c| {
|
||||||
|
c.overlay = Some(value.to_string());
|
||||||
|
});
|
||||||
|
self.mark_changed();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_terminal_scheme(&mut self, value: &str) -> fdo::Result<()> {
|
||||||
|
if Some(value) == self.realm.config().terminal_scheme() {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
let scheme = match Base16Scheme::by_name(value) {
|
||||||
|
Some(scheme) => scheme,
|
||||||
|
None => return failed(format!("Invalid terminal color scheme '{}'", value)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let manager = self.realm.manager();
|
||||||
|
if let Err(err) = scheme.apply_to_realm(&manager, &self.realm) {
|
||||||
|
return failed(format!("Error applying terminal color scheme: {}", err));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.realm.with_mut_config(|c| {
|
||||||
|
c.terminal_scheme = Some(value.to_string());
|
||||||
|
});
|
||||||
|
self.mark_changed();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_realmfs(&mut self, value: &str) -> fdo::Result<()> {
|
||||||
|
let manager = self.realm.manager();
|
||||||
|
if manager.realmfs_by_name(value).is_none() {
|
||||||
|
return failed(format!("Failed to set 'realmfs' config for realm-{}: RealmFS named '{}' does not exist", self.realm.name(), value));
|
||||||
|
}
|
||||||
|
if self.realm.config().realmfs() != value {
|
||||||
|
self.realm.with_mut_config(|c| {
|
||||||
|
c.realmfs = Some(value.to_string())
|
||||||
|
});
|
||||||
|
self.mark_changed();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_config(&self) -> fdo::Result<()> {
|
||||||
|
if self.is_changed() {
|
||||||
|
self.realm.config()
|
||||||
|
.write()
|
||||||
|
.map_err(|err| fdo::Error::Failed(format!("Error writing config file for realm-{}: {}", self.realm.name(), err)))?;
|
||||||
|
|
||||||
|
self.changed.store(false, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_var(&mut self, var: &str, value: &str) -> fdo::Result<()> {
|
||||||
|
if is_bool_config_variable(var) {
|
||||||
|
self.set_bool_var(var, value)
|
||||||
|
} else if var == "overlay" {
|
||||||
|
self.set_overlay(value)
|
||||||
|
} else if var == "terminal-scheme" {
|
||||||
|
self.set_terminal_scheme(value)
|
||||||
|
} else if var == "realmfs" {
|
||||||
|
self.set_realmfs(value)
|
||||||
|
} else {
|
||||||
|
failed(format!("Unknown realm configuration variable '{}'", var))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
103
realmsd/src/next/manager.rs
Normal file
103
realmsd/src/next/manager.rs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
use blocking::unblock;
|
||||||
|
use event_listener::{Event, EventListener};
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde_repr::Serialize_repr;
|
||||||
|
use zbus::blocking::Connection;
|
||||||
|
use zbus::{fdo, interface};
|
||||||
|
use zvariant::Type;
|
||||||
|
use libcitadel::{PidLookupResult, RealmManager};
|
||||||
|
use crate::next::config::RealmConfigVars;
|
||||||
|
use crate::next::realm::RealmItemState;
|
||||||
|
use crate::next::realmfs::RealmFSState;
|
||||||
|
|
||||||
|
pub fn failed<T>(message: String) -> fdo::Result<T> {
|
||||||
|
Err(fdo::Error::Failed(message))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize_repr, Type, Debug, PartialEq)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum PidLookupResultCode {
|
||||||
|
Unknown = 1,
|
||||||
|
Realm = 2,
|
||||||
|
Citadel = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Type, Serialize)]
|
||||||
|
pub struct RealmFromCitadelPid {
|
||||||
|
code: PidLookupResultCode,
|
||||||
|
realm: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PidLookupResult> for RealmFromCitadelPid {
|
||||||
|
fn from(result: PidLookupResult) -> Self {
|
||||||
|
match result {
|
||||||
|
PidLookupResult::Unknown => RealmFromCitadelPid { code: PidLookupResultCode::Unknown, realm: String::new() },
|
||||||
|
PidLookupResult::Realm(realm) => RealmFromCitadelPid { code: PidLookupResultCode::Realm, realm: realm.name().to_string() },
|
||||||
|
PidLookupResult::Citadel => RealmFromCitadelPid { code: PidLookupResultCode::Citadel, realm: String::new() },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RealmsManagerServer2 {
|
||||||
|
realms: RealmItemState,
|
||||||
|
realmfs_state: RealmFSState,
|
||||||
|
manager: Arc<RealmManager>,
|
||||||
|
quit_event: Arc<Event>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl RealmsManagerServer2 {
|
||||||
|
|
||||||
|
fn new(connection: Connection, manager: Arc<RealmManager>, quit_event: Arc<Event>) -> Self {
|
||||||
|
let realms = RealmItemState::new(connection.clone());
|
||||||
|
let realmfs_state = RealmFSState::new(connection.clone());
|
||||||
|
RealmsManagerServer2 {
|
||||||
|
realms,
|
||||||
|
realmfs_state,
|
||||||
|
manager,
|
||||||
|
quit_event,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load(connection: &Connection, manager: Arc<RealmManager>, quit_event: Arc<Event>) -> zbus::Result<Self> {
|
||||||
|
let mut server = Self::new(connection.clone(), manager.clone(), quit_event);
|
||||||
|
server.realms.load_realms(&manager)?;
|
||||||
|
server.realmfs_state.load(&manager)?;
|
||||||
|
server.realms.populate_realmfs(&server.realmfs_state)?;
|
||||||
|
Ok(server)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[interface(name = "com.subgraph.realms.Manager2")]
|
||||||
|
impl RealmsManagerServer2 {
|
||||||
|
|
||||||
|
async fn get_current(&self) -> u32 {
|
||||||
|
|
||||||
|
self.realms.get_current()
|
||||||
|
.map(|r| r.index())
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn realm_from_citadel_pid(&self, pid: u32) -> RealmFromCitadelPid {
|
||||||
|
let manager = self.manager.clone();
|
||||||
|
unblock(move || {
|
||||||
|
manager.realm_by_pid(pid).into()
|
||||||
|
}).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_realm(&self, name: &str) -> fdo::Result<()> {
|
||||||
|
let manager = self.manager.clone();
|
||||||
|
let name = name.to_string();
|
||||||
|
unblock(move || {
|
||||||
|
let _ = manager.new_realm(&name).map_err(|err| fdo::Error::Failed(err.to_string()))?;
|
||||||
|
Ok(())
|
||||||
|
}).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_global_config(&self) -> RealmConfigVars {
|
||||||
|
RealmConfigVars::new_global()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
8
realmsd/src/next/mod.rs
Normal file
8
realmsd/src/next/mod.rs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
mod manager;
|
||||||
|
mod config;
|
||||||
|
mod realm;
|
||||||
|
mod realmfs;
|
||||||
|
|
||||||
|
pub use manager::RealmsManagerServer2;
|
||||||
|
pub const REALMS2_SERVER_OBJECT_PATH: &str = "/com/subgraph/Realms2";
|
374
realmsd/src/next/realm.rs
Normal file
374
realmsd/src/next/realm.rs
Normal file
@ -0,0 +1,374 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
|
use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU32, Ordering};
|
||||||
|
use blocking::unblock;
|
||||||
|
use zbus::{interface, fdo};
|
||||||
|
use zbus::blocking::Connection;
|
||||||
|
use zbus::names::{BusName, InterfaceName};
|
||||||
|
use zvariant::{OwnedObjectPath, Value};
|
||||||
|
use libcitadel::{Realm, RealmEvent, RealmManager, Result};
|
||||||
|
use crate::next::config::{RealmConfig, RealmConfigVars};
|
||||||
|
use crate::next::realmfs::RealmFSState;
|
||||||
|
use crate::next::REALMS2_SERVER_OBJECT_PATH;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct RealmItem {
|
||||||
|
path: String,
|
||||||
|
index: u32,
|
||||||
|
realm: Realm,
|
||||||
|
config: RealmConfig,
|
||||||
|
in_run_transition: Arc<AtomicBool>,
|
||||||
|
realmfs_index: Arc<AtomicU32>,
|
||||||
|
last_timestamp: Arc<AtomicI64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy,Clone)]
|
||||||
|
#[repr(u32)]
|
||||||
|
enum RealmRunStatus {
|
||||||
|
Stopped = 0,
|
||||||
|
Starting,
|
||||||
|
Running,
|
||||||
|
Current,
|
||||||
|
Stopping,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RealmRunStatus {
|
||||||
|
fn for_realm(realm: &Realm, in_transition: bool) -> Self {
|
||||||
|
if in_transition {
|
||||||
|
if realm.is_active() { Self::Stopping } else { Self::Starting }
|
||||||
|
} else if realm.is_active() {
|
||||||
|
if realm.is_current() { Self::Current } else {Self::Running }
|
||||||
|
} else {
|
||||||
|
Self::Stopped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RealmItem {
|
||||||
|
pub(crate) fn new_from_realm(index: u32, realm: Realm) -> RealmItem {
|
||||||
|
let path = format!("{}/Realm{}", REALMS2_SERVER_OBJECT_PATH, index);
|
||||||
|
let in_run_transition = Arc::new(AtomicBool::new(false));
|
||||||
|
let config = RealmConfig::new(realm.clone());
|
||||||
|
let realmfs_index = Arc::new(AtomicU32::new(0));
|
||||||
|
let last_timestamp = Arc::new(AtomicI64::new(realm.timestamp()));
|
||||||
|
RealmItem { path, index, realm, config, in_run_transition, realmfs_index, last_timestamp }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path(&self) -> &str {
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn index(&self) -> u32 {
|
||||||
|
self.index
|
||||||
|
}
|
||||||
|
|
||||||
|
fn in_run_transition(&self) -> bool {
|
||||||
|
self.in_run_transition.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_run_status(&self) -> RealmRunStatus {
|
||||||
|
RealmRunStatus::for_realm(&self.realm, self.in_run_transition())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_start(&mut self) -> fdo::Result<()> {
|
||||||
|
if !self.realm.is_active() {
|
||||||
|
let realm = self.realm.clone();
|
||||||
|
|
||||||
|
let res = unblock(move || realm.start()).await;
|
||||||
|
|
||||||
|
if let Err(err) = res {
|
||||||
|
return Err(fdo::Error::Failed(format!("Failed to start realm: {}", err)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_stop(&mut self) -> fdo::Result<()> {
|
||||||
|
if self.realm.is_active() {
|
||||||
|
let realm = self.realm.clone();
|
||||||
|
|
||||||
|
let res = unblock(move || realm.stop()).await;
|
||||||
|
|
||||||
|
if let Err(err) = res {
|
||||||
|
return Err(fdo::Error::Failed(format!("Failed to stop realm: {}", err)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[interface(
|
||||||
|
name = "com.subgraph.realms.Realm"
|
||||||
|
)]
|
||||||
|
impl RealmItem {
|
||||||
|
|
||||||
|
async fn start(
|
||||||
|
&mut self,
|
||||||
|
) -> fdo::Result<()> {
|
||||||
|
|
||||||
|
self.do_start().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn stop(
|
||||||
|
&mut self,
|
||||||
|
) -> fdo::Result<()> {
|
||||||
|
self.do_stop().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn restart(
|
||||||
|
&mut self,
|
||||||
|
) -> fdo::Result<()> {
|
||||||
|
self.do_stop().await?;
|
||||||
|
self.do_start().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_current(&mut self) -> fdo::Result<()> {
|
||||||
|
let realm = self.realm.clone();
|
||||||
|
let res = unblock(move || realm.set_current()).await;
|
||||||
|
if let Err(err) = res {
|
||||||
|
return Err(fdo::Error::Failed(format!("Failed to set realm {} as current: {}", self.realm.name(), err)));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_config(&self) -> RealmConfigVars {
|
||||||
|
self.config.config_vars()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_config(&mut self, vars: Vec<(String, String)>) -> fdo::Result<()> {
|
||||||
|
for (var, val) in &vars {
|
||||||
|
self.config.set_var(var, val)?;
|
||||||
|
}
|
||||||
|
let config = self.config.clone();
|
||||||
|
unblock(move || config.save_config()).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property, name = "RunStatus")]
|
||||||
|
fn run_status(&self) -> u32 {
|
||||||
|
self.get_run_status() as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property, name="IsSystemRealm")]
|
||||||
|
fn is_system_realm(&self) -> bool {
|
||||||
|
self.realm.is_system()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property, name = "Name")]
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
self.realm.name()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property, name = "Description")]
|
||||||
|
fn description(&self) -> String {
|
||||||
|
self.realm.notes()
|
||||||
|
.unwrap_or(String::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property, name = "PidNS")]
|
||||||
|
fn pid_ns(&self) -> u64 {
|
||||||
|
self.realm.pid_ns().unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property, name = "RealmFS")]
|
||||||
|
fn realmfs(&self) -> u32 {
|
||||||
|
self.realmfs_index.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property, name = "Timestamp")]
|
||||||
|
fn timestamp(&self) -> u64 {
|
||||||
|
self.realm.timestamp() as u64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct RealmItemState(Arc<Mutex<Inner>>);
|
||||||
|
|
||||||
|
struct Inner {
|
||||||
|
connection: Connection,
|
||||||
|
next_index: u32,
|
||||||
|
realms: HashMap<String, RealmItem>,
|
||||||
|
current_realm: Option<RealmItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Inner {
|
||||||
|
fn new(connection: Connection) -> Self {
|
||||||
|
Inner {
|
||||||
|
connection,
|
||||||
|
next_index: 1,
|
||||||
|
realms:HashMap::new(),
|
||||||
|
current_realm: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_realms(&mut self, manager: &RealmManager) -> zbus::Result<()> {
|
||||||
|
for realm in manager.realm_list() {
|
||||||
|
self.add_realm(realm)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn populate_realmfs(&mut self, realmfs_state: &RealmFSState) -> zbus::Result<()> {
|
||||||
|
for item in self.realms.values_mut() {
|
||||||
|
if let Some(realmfs) = realmfs_state.realmfs_by_name(item.realm.config().realmfs()) {
|
||||||
|
item.realmfs_index.store(realmfs.index(), Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_realm(&mut self, realm: Realm) -> zbus::Result<()> {
|
||||||
|
if self.realms.contains_key(realm.name()) {
|
||||||
|
warn!("Attempted to add duplicate realm '{}'", realm.name());
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = realm.name().to_string();
|
||||||
|
let item = RealmItem::new_from_realm(self.next_index, realm);
|
||||||
|
self.connection.object_server().at(item.path(), item.clone())?;
|
||||||
|
self.realms.insert(key, item);
|
||||||
|
self.next_index += 1;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_realm(&mut self, realm: &Realm) -> zbus::Result<()> {
|
||||||
|
if let Some(item) = self.realms.remove(realm.name()) {
|
||||||
|
self.connection.object_server().remove::<RealmItem, &str>(item.path())?;
|
||||||
|
} else {
|
||||||
|
warn!("Failed to find realm to remove with name '{}'", realm.name());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_property_changed(&self, object_path: OwnedObjectPath, propname: &str, value: Value<'_>) -> zbus::Result<()> {
|
||||||
|
let iface_name = InterfaceName::from_str_unchecked("com.subgraph.realms.Realm");
|
||||||
|
let changed = HashMap::from([(propname.to_string(), value)]);
|
||||||
|
let inval: &[&str] = &[];
|
||||||
|
self.connection.emit_signal(
|
||||||
|
None::<BusName<'_>>,
|
||||||
|
&object_path,
|
||||||
|
"org.freedesktop.Dbus.Properties",
|
||||||
|
"PropertiesChanged",
|
||||||
|
&(iface_name, changed, inval))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn realm_status_changed(&self, realm: &Realm, transition: Option<bool>) -> zbus::Result<()> {
|
||||||
|
if let Some(realm) = self.realm_by_name(realm.name()) {
|
||||||
|
if let Some(transition) = transition {
|
||||||
|
realm.in_run_transition.store(transition, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
let object_path = realm.path().try_into().unwrap();
|
||||||
|
self.emit_property_changed(object_path, "RunStatus", Value::U32(realm.get_run_status() as u32))?;
|
||||||
|
let timestamp = realm.realm.timestamp();
|
||||||
|
if realm.last_timestamp.load(Ordering::Relaxed) != realm.realm.timestamp() {
|
||||||
|
realm.last_timestamp.store(timestamp, Ordering::Relaxed);
|
||||||
|
let object_path = realm.path().try_into().unwrap();
|
||||||
|
self.emit_property_changed(object_path, "Timestamp", Value::U64(timestamp as u64))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn realm_by_name(&self, name: &str) -> Option<&RealmItem> {
|
||||||
|
let res = self.realms.get(name);
|
||||||
|
|
||||||
|
if res.is_none() {
|
||||||
|
warn!("Failed to find realm with name '{}'", name);
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_starting(&self, realm: &Realm) -> zbus::Result<()>{
|
||||||
|
self.realm_status_changed(realm, Some(true))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_started(&self, realm: &Realm) -> zbus::Result<()>{
|
||||||
|
self.realm_status_changed(realm, Some(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_stopping(&self, realm: &Realm) -> zbus::Result<()> {
|
||||||
|
self.realm_status_changed(realm, Some(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_stopped(&self, realm: &Realm) -> zbus::Result<()> {
|
||||||
|
self.realm_status_changed(realm, Some(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_new(&mut self, realm: &Realm) -> zbus::Result<()> {
|
||||||
|
self.add_realm(realm.clone())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_removed(&mut self, realm: &Realm) -> zbus::Result<()> {
|
||||||
|
self.remove_realm(&realm)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_current(&mut self, realm: Option<&Realm>) -> zbus::Result<()> {
|
||||||
|
|
||||||
|
if let Some(r) = self.current_realm.take() {
|
||||||
|
self.realm_status_changed(&r.realm, None)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(realm) = realm {
|
||||||
|
self.realm_status_changed(realm, None)?;
|
||||||
|
if let Some(item) = self.realm_by_name(realm.name()) {
|
||||||
|
self.current_realm = Some(item.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RealmItemState {
|
||||||
|
pub fn new(connection: Connection) -> Self {
|
||||||
|
RealmItemState(Arc::new(Mutex::new(Inner::new(connection))))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_realms(&self, manager: &RealmManager) -> zbus::Result<()> {
|
||||||
|
self.inner().load_realms(manager)?;
|
||||||
|
self.add_event_handler(manager)
|
||||||
|
.map_err(|err| zbus::Error::Failure(err.to_string()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn populate_realmfs(&self, realmfs_state: &RealmFSState) -> zbus::Result<()> {
|
||||||
|
self.inner().populate_realmfs(realmfs_state)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_current(&self) -> Option<RealmItem> {
|
||||||
|
self.inner().current_realm.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inner(&self) -> MutexGuard<Inner> {
|
||||||
|
self.0.lock().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_event_handler(&self, manager: &RealmManager) -> Result<()> {
|
||||||
|
let state = self.clone();
|
||||||
|
manager.add_event_handler(move |ev| {
|
||||||
|
if let Err(err) = state.handle_event(ev) {
|
||||||
|
warn!("Error handling {}: {}", ev, err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
manager.start_event_task()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_event(&self, ev: &RealmEvent) -> zbus::Result<()> {
|
||||||
|
match ev {
|
||||||
|
RealmEvent::Started(realm) => self.inner().on_started(realm)?,
|
||||||
|
RealmEvent::Stopped(realm) => self.inner().on_stopped(realm)?,
|
||||||
|
RealmEvent::New(realm) => self.inner().on_new(realm)?,
|
||||||
|
RealmEvent::Removed(realm) => self.inner().on_removed(realm)?,
|
||||||
|
RealmEvent::Current(realm) => self.inner().on_current(realm.as_ref())?,
|
||||||
|
RealmEvent::Starting(realm) => self.inner().on_starting(realm)?,
|
||||||
|
RealmEvent::Stopping(realm) => self.inner().on_stopping(realm)?,
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
120
realmsd/src/next/realmfs.rs
Normal file
120
realmsd/src/next/realmfs.rs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use zbus::blocking::Connection;
|
||||||
|
use zbus::{fdo, interface};
|
||||||
|
use zvariant::{ObjectPath, OwnedObjectPath};
|
||||||
|
use libcitadel::{RealmFS, RealmManager};
|
||||||
|
use crate::next::REALMS2_SERVER_OBJECT_PATH;
|
||||||
|
|
||||||
|
const BLOCK_SIZE: u64 = 4096;
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct RealmFSItem {
|
||||||
|
object_path: OwnedObjectPath,
|
||||||
|
index: u32,
|
||||||
|
realmfs: RealmFS,
|
||||||
|
}
|
||||||
|
impl RealmFSItem {
|
||||||
|
pub(crate) fn new_from_realmfs(index: u32, realmfs: RealmFS) -> RealmFSItem {
|
||||||
|
let object_path = format!("{}/RealmFS{}", REALMS2_SERVER_OBJECT_PATH, index).try_into().unwrap();
|
||||||
|
RealmFSItem {
|
||||||
|
object_path,
|
||||||
|
index,
|
||||||
|
realmfs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn index(&self) -> u32 {
|
||||||
|
self.index
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn object_path(&self) -> ObjectPath {
|
||||||
|
self.object_path.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[interface(
|
||||||
|
name = "com.subgraph.realms.RealmFS"
|
||||||
|
)]
|
||||||
|
impl RealmFSItem {
|
||||||
|
|
||||||
|
#[zbus(property, name = "Name")]
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
self.realmfs.name()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property, name = "Activated")]
|
||||||
|
fn activated(&self) -> bool {
|
||||||
|
self.realmfs.is_activated()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property, name = "InUse")]
|
||||||
|
fn in_use(&self) -> bool {
|
||||||
|
self.realmfs.is_activated()
|
||||||
|
}
|
||||||
|
#[zbus(property, name = "Mountpoint")]
|
||||||
|
fn mountpoint(&self) -> String {
|
||||||
|
self.realmfs.mountpoint().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property, name = "Path")]
|
||||||
|
fn path(&self) -> String {
|
||||||
|
format!("{}", self.realmfs.path().display())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property, name = "FreeSpace")]
|
||||||
|
fn free_space(&self) -> fdo::Result<u64> {
|
||||||
|
let blocks = self.realmfs.free_size_blocks()
|
||||||
|
.map_err(|err| fdo::Error::Failed(err.to_string()))?;
|
||||||
|
Ok(blocks as u64 * BLOCK_SIZE)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[zbus(property, name = "AllocatedSpace")]
|
||||||
|
fn allocated_space(&self) -> fdo::Result<u64> {
|
||||||
|
let blocks = self.realmfs.allocated_size_blocks()
|
||||||
|
.map_err(|err| fdo::Error::Failed(err.to_string()))?;
|
||||||
|
Ok(blocks as u64 * BLOCK_SIZE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RealmFSState {
|
||||||
|
connection: Connection,
|
||||||
|
next_index: u32,
|
||||||
|
items: HashMap<String, RealmFSItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RealmFSState {
|
||||||
|
pub fn new(connection: Connection) -> Self {
|
||||||
|
RealmFSState {
|
||||||
|
connection,
|
||||||
|
next_index: 1,
|
||||||
|
items: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load(&mut self, manager: &RealmManager) -> zbus::Result<()> {
|
||||||
|
for realmfs in manager.realmfs_list() {
|
||||||
|
self.add_realmfs(realmfs)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_realmfs(&mut self, realmfs: RealmFS) -> zbus::Result<()> {
|
||||||
|
if !self.items.contains_key(realmfs.name()) {
|
||||||
|
let name = realmfs.name().to_string();
|
||||||
|
let item = RealmFSItem::new_from_realmfs(self.next_index, realmfs);
|
||||||
|
self.connection.object_server().at(item.object_path(), item.clone())?;
|
||||||
|
self.items.insert(name, item);
|
||||||
|
self.next_index += 1;
|
||||||
|
} else {
|
||||||
|
warn!("Attempted to add duplicate realmfs '{}'", realmfs.name());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn realmfs_by_name(&self, name: &str) -> Option<&RealmFSItem> {
|
||||||
|
let res = self.items.get(name);
|
||||||
|
if res.is_none() {
|
||||||
|
warn!("Failed to find RealmFS with name '{}'", name);
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,14 @@
|
|||||||
use libcitadel::{RealmManager, Realm, OverlayType, Result, PidLookupResult};
|
use libcitadel::{RealmManager, Realm, OverlayType, Result, PidLookupResult};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use zbus::{dbus_interface, ObjectServer,Connection};
|
use zbus::blocking::Connection;
|
||||||
use zvariant::derive::Type;
|
use zvariant::Type;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use blocking::unblock;
|
||||||
|
use event_listener::{Event, EventListener};
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use serde_repr::Serialize_repr;
|
use serde_repr::Serialize_repr;
|
||||||
|
use zbus::{interface, SignalContext};
|
||||||
use crate::events::EventHandler;
|
use crate::events::EventHandler;
|
||||||
use libcitadel::terminal::Base16Scheme;
|
use libcitadel::terminal::Base16Scheme;
|
||||||
|
|
||||||
@ -39,6 +42,7 @@ impl From<PidLookupResult> for RealmFromCitadelPid {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct RealmsManagerServer {
|
pub struct RealmsManagerServer {
|
||||||
manager: Arc<RealmManager>,
|
manager: Arc<RealmManager>,
|
||||||
|
quit_event: Arc<Event>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const BOOL_CONFIG_VARS: &[&str] = &[
|
const BOOL_CONFIG_VARS: &[&str] = &[
|
||||||
@ -121,40 +125,40 @@ fn configure_realm(manager: &RealmManager, realm: &Realm, variable: &str, value:
|
|||||||
|
|
||||||
impl RealmsManagerServer {
|
impl RealmsManagerServer {
|
||||||
|
|
||||||
fn register_events(&self, connection: &Connection) -> Result<()> {
|
pub fn load(connection: &Connection, manager: Arc<RealmManager>, quit_event: Arc<Event>) -> Result<RealmsManagerServer> {
|
||||||
let events = EventHandler::new(connection.clone(), self.clone());
|
let server = RealmsManagerServer { manager, quit_event };
|
||||||
self.manager.add_event_handler(move |ev| events.handle_event(ev));
|
let events = EventHandler::new(connection.clone());
|
||||||
self.manager.start_event_task()
|
server.manager.add_event_handler(move |ev| events.handle_event(ev));
|
||||||
|
server.manager.start_event_task()?;
|
||||||
|
Ok(server)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register(connection: &Connection) -> Result<ObjectServer> {
|
|
||||||
let manager = RealmManager::load()?;
|
|
||||||
let iface = RealmsManagerServer { manager };
|
|
||||||
iface.register_events(connection)?;
|
|
||||||
let mut object_server = ObjectServer::new(connection);
|
|
||||||
object_server.at(REALMS_SERVER_OBJECT_PATH, iface).map_err(context!("ZBus error"))?;
|
|
||||||
Ok(object_server)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[dbus_interface(name = "com.subgraph.realms.Manager")]
|
#[interface(name = "com.subgraph.realms.Manager")]
|
||||||
impl RealmsManagerServer {
|
impl RealmsManagerServer {
|
||||||
|
|
||||||
fn set_current(&self, name: &str) {
|
async fn set_current(&self, name: &str) {
|
||||||
if let Some(realm) = self.manager.realm_by_name(name) {
|
|
||||||
if let Err(err) = self.manager.set_current_realm(&realm) {
|
let manager = self.manager.clone();
|
||||||
|
let name = name.to_string();
|
||||||
|
unblock(move || {
|
||||||
|
if let Some(realm) = manager.realm_by_name(&name) {
|
||||||
|
if let Err(err) = manager.set_current_realm(&realm) {
|
||||||
warn!("set_current_realm({}) failed: {}", name, err);
|
warn!("set_current_realm({}) failed: {}", name, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_current(&self) -> String {
|
async fn get_current(&self) -> String {
|
||||||
match self.manager.current_realm() {
|
let manager = self.manager.clone();
|
||||||
|
unblock(move || {
|
||||||
|
match manager.current_realm() {
|
||||||
Some(realm) => realm.name().to_string(),
|
Some(realm) => realm.name().to_string(),
|
||||||
None => String::new(),
|
None => String::new(),
|
||||||
}
|
}
|
||||||
|
}).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list(&self) -> Vec<RealmItem> {
|
fn list(&self) -> Vec<RealmItem> {
|
||||||
@ -249,8 +253,12 @@ impl RealmsManagerServer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realm_from_citadel_pid(&self, pid: u32) -> RealmFromCitadelPid {
|
async fn realm_from_citadel_pid(&self, pid: u32) -> RealmFromCitadelPid {
|
||||||
self.manager.realm_by_pid(pid).into()
|
let manager = self.manager.clone();
|
||||||
|
unblock(move || {
|
||||||
|
manager.realm_by_pid(pid).into()
|
||||||
|
|
||||||
|
}).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realm_config(&self, name: &str) -> RealmConfig {
|
fn realm_config(&self, name: &str) -> RealmConfig {
|
||||||
@ -261,7 +269,7 @@ impl RealmsManagerServer {
|
|||||||
RealmConfig::new_from_realm(&realm)
|
RealmConfig::new_from_realm(&realm)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realm_set_config(&self, name: &str, vars: Vec<(String,String)>) {
|
async fn realm_set_config(&self, name: &str, vars: Vec<(String,String)>) {
|
||||||
let realm = match self.manager.realm_by_name(name) {
|
let realm = match self.manager.realm_by_name(name) {
|
||||||
Some(r) => r,
|
Some(r) => r,
|
||||||
None => {
|
None => {
|
||||||
@ -270,8 +278,12 @@ impl RealmsManagerServer {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
for var in &vars {
|
for var in vars {
|
||||||
configure_realm(&self.manager, &realm, &var.0, &var.1);
|
let manager = self.manager.clone();
|
||||||
|
let realm = realm.clone();
|
||||||
|
unblock( move || {
|
||||||
|
configure_realm(&manager, &realm, &var.0, &var.1);
|
||||||
|
}).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,13 +291,18 @@ impl RealmsManagerServer {
|
|||||||
Realm::is_valid_name(name) && self.manager.realm_by_name(name).is_some()
|
Realm::is_valid_name(name) && self.manager.realm_by_name(name).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_realm(&self, name: &str) -> bool {
|
async fn create_realm(&self, name: &str) -> bool {
|
||||||
if let Err(err) = self.manager.new_realm(name) {
|
|
||||||
|
let manager = self.manager.clone();
|
||||||
|
let name = name.to_string();
|
||||||
|
unblock(move || {
|
||||||
|
if let Err(err) = manager.new_realm(&name) {
|
||||||
warn!("Error creating realm ({}): {}", name, err);
|
warn!("Error creating realm ({}): {}", name, err);
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
}).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_realm_f_s(&self) -> Vec<String> {
|
fn list_realm_f_s(&self) -> Vec<String> {
|
||||||
@ -299,23 +316,23 @@ impl RealmsManagerServer {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[dbus_interface(signal)]
|
#[zbus(signal)]
|
||||||
pub fn realm_started(&self, realm: &str, pid_ns: u64, status: u8) -> zbus::Result<()> { Ok(()) }
|
pub async fn realm_started(ctx: &SignalContext<'_>, realm: &str, pid_ns: u64, status: u8) -> zbus::Result<()>;
|
||||||
|
|
||||||
#[dbus_interface(signal)]
|
#[zbus(signal)]
|
||||||
pub fn realm_stopped(&self, realm: &str, status: u8) -> zbus::Result<()> { Ok(()) }
|
pub async fn realm_stopped(ctx: &SignalContext<'_>, realm: &str, status: u8) -> zbus::Result<()>;
|
||||||
|
|
||||||
#[dbus_interface(signal)]
|
#[zbus(signal)]
|
||||||
pub fn realm_new(&self, realm: &str, description: &str, status: u8) -> zbus::Result<()> { Ok(()) }
|
pub async fn realm_new(ctx: &SignalContext<'_>, realm: &str, description: &str, status: u8) -> zbus::Result<()>;
|
||||||
|
|
||||||
#[dbus_interface(signal)]
|
#[zbus(signal)]
|
||||||
pub fn realm_removed(&self, realm: &str) -> zbus::Result<()> { Ok(()) }
|
pub async fn realm_removed(ctx: &SignalContext<'_>, realm: &str) -> zbus::Result<()>;
|
||||||
|
|
||||||
#[dbus_interface(signal)]
|
#[zbus(signal)]
|
||||||
pub fn realm_current(&self, realm: &str, status: u8) -> zbus::Result<()> { Ok(()) }
|
pub async fn realm_current(ctx: &SignalContext<'_>, realm: &str, status: u8) -> zbus::Result<()>;
|
||||||
|
|
||||||
#[dbus_interface(signal)]
|
#[zbus(signal)]
|
||||||
pub fn service_started(&self) -> zbus::Result<()> { Ok(()) }
|
pub async fn service_started(ctx: &SignalContext<'_>) -> zbus::Result<()>;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=Current realm directory watcher
|
Description=Current realm directory watcher
|
||||||
Before=launch-default-realm.service
|
|
||||||
|
|
||||||
[Path]
|
[Path]
|
||||||
PathChanged=/run/citadel/realms/current
|
PathChanged=/run/citadel/realms/current
|
||||||
|
@ -4,4 +4,6 @@ StartLimitIntervalSec=0
|
|||||||
|
|
||||||
[Path]
|
[Path]
|
||||||
PathChanged=/run/citadel/realms/current/current.realm/rootfs/usr/share/applications
|
PathChanged=/run/citadel/realms/current/current.realm/rootfs/usr/share/applications
|
||||||
|
PathChanged=/run/citadel/realms/current/current.realm/flatpak/exports/share/applications
|
||||||
PathChanged=/run/citadel/realms/current/current.realm/home/.local/share/applications
|
PathChanged=/run/citadel/realms/current/current.realm/home/.local/share/applications
|
||||||
|
PathChanged=/run/citadel/realms/current/current.realm/home/.local/share/flatpak/exports/share/applications
|
||||||
|
Loading…
Reference in New Issue
Block a user