[WIP] Daemon & CLI
This commit is contained in:
parent
56aad3a023
commit
8b158108bb
100 changed files with 6519 additions and 1596 deletions
11
iota-installer/Cargo.toml
Normal file
11
iota-installer/Cargo.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "iota-installer"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
tempfile = "3"
|
||||
zip = "6"
|
||||
serde_json = "1"
|
||||
iota-paths = { path = "../iota-paths" }
|
||||
187
iota-installer/src/lib.rs
Normal file
187
iota-installer/src/lib.rs
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
use anyhow::{Context, Result, bail};
|
||||
use std::{fs, io, path::Path, process::Command};
|
||||
use tempfile::tempdir;
|
||||
use zip::ZipArchive;
|
||||
|
||||
const REQUIRED: &[&str] = &[
|
||||
"bin/iota",
|
||||
"bin/iota-daemon",
|
||||
"bin/iota-updater",
|
||||
"systemd/iota-daemon.service",
|
||||
"systemd/iota-daemon.socket",
|
||||
"systemd/sysusers.d/iota.conf",
|
||||
"systemd/iota-update.service",
|
||||
"systemd/iota-update.timer",
|
||||
"manifest.json",
|
||||
];
|
||||
|
||||
pub fn install_linux_bundle(bundle: &Path) -> Result<()> {
|
||||
install_linux_bundle_with_operator(bundle, None)
|
||||
}
|
||||
|
||||
pub fn install_linux_bundle_with_operator(bundle: &Path, operator: Option<&str>) -> Result<()> {
|
||||
if std::env::consts::OS != "linux" {
|
||||
bail!("Linux systemd bundles are not supported on this platform");
|
||||
}
|
||||
let staging = tempdir().context("create installer staging directory")?;
|
||||
let file = fs::File::open(bundle).context("open release bundle")?;
|
||||
let mut archive = ZipArchive::new(file).context("read release bundle")?;
|
||||
for name in REQUIRED {
|
||||
let mut entry = archive
|
||||
.by_name(name)
|
||||
.with_context(|| format!("bundle is missing {name}"))?;
|
||||
let output = staging.path().join(name);
|
||||
if let Some(parent) = output.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
let mut out = fs::File::create(&output)?;
|
||||
io::copy(&mut entry, &mut out)?;
|
||||
}
|
||||
|
||||
install(
|
||||
&staging.path().join("bin/iota"),
|
||||
&format!(
|
||||
"{}/versions/{}/bin/iota",
|
||||
iota_paths::install_root().display(),
|
||||
product_version(staging.path())
|
||||
),
|
||||
"0755",
|
||||
)?;
|
||||
install(
|
||||
&staging.path().join("bin/iota-daemon"),
|
||||
&format!(
|
||||
"{}/versions/{}/bin/iota-daemon",
|
||||
iota_paths::install_root().display(),
|
||||
product_version(staging.path())
|
||||
),
|
||||
"0755",
|
||||
)?;
|
||||
let version_dir = format!(
|
||||
"{}/versions/{}",
|
||||
iota_paths::install_root().display(),
|
||||
product_version(staging.path())
|
||||
);
|
||||
if !Path::new(&format!("{version_dir}/bin/iota-daemon")).is_file() {
|
||||
bail!("installed daemon executable is missing: {version_dir}/bin/iota-daemon");
|
||||
}
|
||||
install(
|
||||
&staging.path().join("bin/iota-updater"),
|
||||
&format!(
|
||||
"{}/versions/{}/bin/iota-updater",
|
||||
iota_paths::install_root().display(),
|
||||
product_version(staging.path())
|
||||
),
|
||||
"0755",
|
||||
)?;
|
||||
for unit in [
|
||||
"iota-daemon.service",
|
||||
"iota-daemon.socket",
|
||||
"iota-update.service",
|
||||
"iota-update.timer",
|
||||
] {
|
||||
install(
|
||||
&staging.path().join("systemd").join(unit),
|
||||
&format!("/etc/systemd/system/{unit}"),
|
||||
"0644",
|
||||
)?;
|
||||
}
|
||||
install(
|
||||
&staging.path().join("systemd/sysusers.d/iota.conf"),
|
||||
"/etc/sysusers.d/iota.conf",
|
||||
"0644",
|
||||
)?;
|
||||
run(
|
||||
"ln",
|
||||
&[
|
||||
"-sfn",
|
||||
&version_dir,
|
||||
&iota_paths::current_version_link().to_string_lossy(),
|
||||
],
|
||||
)?;
|
||||
run(
|
||||
"ln",
|
||||
&[
|
||||
"-sfn",
|
||||
&format!("{}/current/bin/iota", iota_paths::install_root().display()),
|
||||
"/usr/local/bin/iota",
|
||||
],
|
||||
)?;
|
||||
run(
|
||||
"ln",
|
||||
&[
|
||||
"-sfn",
|
||||
&format!(
|
||||
"{}/current/bin/iota-daemon",
|
||||
iota_paths::install_root().display()
|
||||
),
|
||||
"/usr/local/lib/iota/iota-daemon",
|
||||
],
|
||||
)?;
|
||||
run("systemd-sysusers", &[])?;
|
||||
if let Some(operator) = operator {
|
||||
run("usermod", &["-aG", "iota-operators", operator])?;
|
||||
} else {
|
||||
eprintln!("To grant socket access, run: usermod -aG iota-operators USER");
|
||||
eprintln!(
|
||||
"A new login session is required before supplementary group membership is visible."
|
||||
);
|
||||
}
|
||||
run("systemctl", &["daemon-reload"])?;
|
||||
run("systemctl", &["enable", "--now", "iota-daemon.socket"])?;
|
||||
run("systemctl", &["is-active", "iota-daemon.socket"])?;
|
||||
run("systemctl", &["is-enabled", "iota-daemon.socket"])?;
|
||||
if !Path::new("/run/iota/iota.sock").exists() {
|
||||
bail!("systemd socket is active but /run/iota/iota.sock was not created");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn product_version(staging: &Path) -> String {
|
||||
fs::read_to_string(staging.join("manifest.json"))
|
||||
.ok()
|
||||
.and_then(|value| serde_json::from_str::<serde_json::Value>(&value).ok())
|
||||
.and_then(|value| {
|
||||
value
|
||||
.get("product_version")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(str::to_owned)
|
||||
})
|
||||
.unwrap_or_else(|| "unversioned".into())
|
||||
}
|
||||
|
||||
fn install(source: &Path, destination: &str, mode: &str) -> Result<()> {
|
||||
run(
|
||||
"install",
|
||||
&["-D", "-m", mode, &source.to_string_lossy(), destination],
|
||||
)
|
||||
}
|
||||
|
||||
fn run(program: &str, args: &[&str]) -> Result<()> {
|
||||
let status = Command::new(program)
|
||||
.args(args)
|
||||
.status()
|
||||
.with_context(|| format!("run {program}"))?;
|
||||
if status.success() {
|
||||
Ok(())
|
||||
} else {
|
||||
bail!("{program} failed; run the installer as root")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn service_uses_installed_daemon_and_declared_identities() {
|
||||
let service = include_str!("../../systemd/iota-daemon.service");
|
||||
let socket = include_str!("../../systemd/iota-daemon.socket");
|
||||
let sysusers = include_str!("../../systemd/sysusers.d/iota.conf");
|
||||
assert!(service.contains("ExecStart=/usr/local/lib/iota/iota-daemon"));
|
||||
assert!(service.contains("User=iota"));
|
||||
assert!(service.contains("Group=iota"));
|
||||
assert!(socket.contains("SocketUser=iota"));
|
||||
assert!(socket.contains("SocketGroup=iota-operators"));
|
||||
assert!(socket.contains("NonBlocking=true"));
|
||||
assert!(sysusers.contains("u iota "));
|
||||
assert!(sysusers.contains("g iota-operators"));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue