misc improvements (#2836)

* misc improvements

* kill proc before destroying subcontainer fs

* version bump

* beta.11

* use bind mount explicitly

* Update sdk/base/lib/Effects.ts

Co-authored-by: Dominion5254 <musashidisciple@proton.me>

---------

Co-authored-by: Dominion5254 <musashidisciple@proton.me>
This commit is contained in:
Aiden McClelland
2025-02-21 15:08:22 -07:00
committed by GitHub
parent 40d194672b
commit 80461a78b0
36 changed files with 358 additions and 143 deletions

View File

@@ -14,7 +14,7 @@ keywords = [
name = "start-os"
readme = "README.md"
repository = "https://github.com/Start9Labs/start-os"
version = "0.3.6-alpha.14" # VERSION_BUMP
version = "0.3.6-alpha.15" # VERSION_BUMP
license = "MIT"
[lib]

View File

@@ -2,6 +2,7 @@ use std::cmp::max;
use std::ffi::OsString;
use std::net::IpAddr;
use std::sync::Arc;
use std::time::Duration;
use clap::Parser;
use color_eyre::eyre::eyre;
@@ -149,7 +150,7 @@ pub fn main(args: impl IntoIterator<Item = OsString>) {
.enable_all()
.build()
.expect("failed to initialize runtime");
rt.block_on(async {
let res = rt.block_on(async {
let mut server = WebServer::new(Acceptor::bind_upgradable(
SelfContainedNetworkInterfaceListener::bind(80),
));
@@ -194,7 +195,9 @@ pub fn main(args: impl IntoIterator<Item = OsString>) {
.await
}
}
})
});
rt.shutdown_timeout(Duration::from_secs(60));
res
};
match res {

View File

@@ -1,3 +1,4 @@
use std::backtrace;
use std::collections::{BTreeMap, BTreeSet};
use std::future::Future;
use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
@@ -484,10 +485,12 @@ impl Drop for RpcContext {
fn drop(&mut self) {
#[cfg(feature = "unstable")]
if self.0.is_closed.load(Ordering::SeqCst) {
tracing::info!(
"RpcContext dropped. {} left.",
Arc::strong_count(&self.0) - 1
);
let count = Arc::strong_count(&self.0) - 1;
tracing::info!("RpcContext dropped. {} left.", count);
if count > 0 {
tracing::debug!("{}", backtrace::Backtrace::force_capture());
tracing::debug!("{:?}", eyre!(""))
}
}
}
}

View File

@@ -267,7 +267,7 @@ impl<A: Accept + Send + Sync + 'static> WebServer<A> {
if !runner.is_empty() {
tokio::time::timeout(Duration::from_secs(60), runner)
.await
.ok();
.log_err();
}
}));
Self {

View File

@@ -27,6 +27,7 @@ pub const SIG_CONTEXT: &str = "s9pk";
pub mod compat;
pub mod manifest;
pub mod pack;
pub mod recipe;
/**
/

View File

@@ -26,6 +26,7 @@ use crate::s9pk::merkle_archive::source::{
into_dyn_read, ArchiveSource, DynFileSource, DynRead, FileSource, TmpSource,
};
use crate::s9pk::merkle_archive::{Entry, MerkleArchive};
use crate::s9pk::v2::recipe::DirRecipe;
use crate::s9pk::v2::SIG_CONTEXT;
use crate::s9pk::S9pk;
use crate::util::io::{create_file, open_file, TmpDir};
@@ -363,6 +364,7 @@ pub enum ImageSource {
build_args: Option<BTreeMap<String, BuildArg>>,
},
DockerTag(String),
// Recipe(DirRecipe),
}
impl ImageSource {
pub fn ingredients(&self) -> Vec<PathBuf> {
@@ -399,6 +401,8 @@ impl ImageSource {
working_dir: PathBuf,
#[serde(default)]
user: String,
entrypoint: Option<Vec<String>>,
cmd: Option<Vec<String>>,
}
async move {
match self {
@@ -531,6 +535,8 @@ impl ImageSource {
} else {
config.user.into()
},
entrypoint: config.entrypoint,
cmd: config.cmd,
})
.with_kind(ErrorKind::Serialization)?
.into(),
@@ -607,8 +613,8 @@ fn tar2sqfs(dest: impl AsRef<Path>) -> Result<Command, Error> {
.arg("run")
.arg("-i")
.arg("--rm")
.arg("-v")
.arg(format!("{}:/data:rw", directory.display()))
.arg("--mount")
.arg(format!("type=bind,src={},dst=/data", directory.display()))
.arg("ghcr.io/start9labs/sdk/utils:latest")
.arg("tar2sqfs")
.arg("-q")
@@ -625,6 +631,8 @@ pub struct ImageMetadata {
pub workdir: PathBuf,
#[ts(type = "string")]
pub user: InternedString,
pub entrypoint: Option<Vec<String>>,
pub cmd: Option<Vec<String>>,
}
#[instrument(skip_all)]

View File

@@ -0,0 +1,21 @@
use std::collections::BTreeMap;
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use ts_rs::TS;
use url::Url;
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
pub struct DirRecipe(BTreeMap<PathBuf, Recipe>);
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
#[serde(rename_all = "camelCase")]
pub enum Recipe {
Make(PathBuf),
Wget {
#[ts(type = "string")]
url: Url,
checksum: String,
},
Recipe(DirRecipe),
}

View File

@@ -1,9 +1,11 @@
use std::net::Ipv4Addr;
use rpc_toolkit::{from_fn, from_fn_async, from_fn_blocking, Context, HandlerExt, ParentHandler};
use crate::echo;
use crate::prelude::*;
use crate::service::cli::ContainerCliContext;
use crate::service::effects::context::EffectContext;
use crate::{echo, HOST_IP};
mod action;
pub mod callbacks;
@@ -134,6 +136,10 @@ pub fn handler<C: Context>() -> ParentHandler<C> {
"get-container-ip",
from_fn_async(net::info::get_container_ip).no_cli(),
)
.subcommand(
"get-os-ip",
from_fn(|_: C| Ok::<_, Error>(Ipv4Addr::from(HOST_IP))),
)
.subcommand(
"export-service-interface",
from_fn_async(net::interface::export_service_interface).no_cli(),

View File

@@ -1,6 +1,7 @@
use std::net::Ipv4Addr;
use crate::service::effects::prelude::*;
use crate::HOST_IP;
pub async fn get_container_ip(context: EffectContext) -> Result<Ipv4Addr, Error> {
let context = context.deref()?;

View File

@@ -5,6 +5,7 @@ use models::ImageId;
use tokio::process::Command;
use crate::disk::mount::filesystem::overlayfs::OverlayGuard;
use crate::disk::mount::guard::GenericMountGuard;
use crate::rpc_continuations::Guid;
use crate::service::effects::prelude::*;
use crate::service::persistent_container::Subcontainer;
@@ -40,6 +41,24 @@ pub async fn destroy_subcontainer_fs(
.await
.remove(&guid)
{
#[cfg(feature = "container-runtime")]
if tokio::fs::metadata(overlay.overlay.path().join("proc/1"))
.await
.is_ok()
{
let procfs = context
.seed
.persistent_container
.lxc_container
.get()
.or_not_found("lxc container")?
.rootfs_dir()
.join("proc");
let overlay_path = overlay.overlay.path().to_owned();
tokio::task::spawn_blocking(move || sync::kill_init(&procfs, &overlay_path))
.await
.with_kind(ErrorKind::Unknown)??;
}
overlay.overlay.unmount(true).await?;
} else {
tracing::warn!("Could not find a subcontainer fs to destroy; assumming that it already is destroyed and will be skipping");

View File

@@ -20,6 +20,54 @@ const FWD_SIGNALS: &[c_int] = &[
SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGUSR1, SIGUSR2, SIGVTALRM,
];
pub fn kill_init(procfs: &Path, chroot: &Path) -> Result<(), Error> {
if chroot.join("proc/1").exists() {
let ns_id = procfs::process::Process::new_with_root(chroot.join("proc/1"))
.with_ctx(|_| (ErrorKind::Filesystem, "open subcontainer procfs"))?
.namespaces()
.with_ctx(|_| (ErrorKind::Filesystem, "read subcontainer pid 1 ns"))?
.0
.get(OsStr::new("pid"))
.or_not_found("pid namespace")?
.identifier;
for proc in procfs::process::all_processes_with_root(procfs)
.with_ctx(|_| (ErrorKind::Filesystem, "open procfs"))?
{
let proc = proc.with_ctx(|_| (ErrorKind::Filesystem, "read single process details"))?;
let pid = proc.pid();
if proc
.namespaces()
.with_ctx(|_| (ErrorKind::Filesystem, lazy_format!("read pid {} ns", pid)))?
.0
.get(OsStr::new("pid"))
.map_or(false, |ns| ns.identifier == ns_id)
{
let pids = proc.read::<NSPid>("status").with_ctx(|_| {
(
ErrorKind::Filesystem,
lazy_format!("read pid {} NSpid", pid),
)
})?;
if pids.0.len() == 2 && pids.0[1] == 1 {
nix::sys::signal::kill(Pid::from_raw(pid), nix::sys::signal::SIGKILL)
.with_ctx(|_| {
(
ErrorKind::Filesystem,
lazy_format!(
"kill pid {} (determined to be pid 1 in subcontainer)",
pid
),
)
})?;
}
}
}
nix::mount::umount(&chroot.join("proc"))
.with_ctx(|_| (ErrorKind::Filesystem, "unmounting subcontainer procfs"))?;
}
Ok(())
}
struct NSPid(Vec<i32>);
impl procfs::FromBufRead for NSPid {
fn from_buf_read<R: std::io::BufRead>(r: R) -> procfs::ProcResult<Self> {
@@ -98,21 +146,27 @@ impl ExecParams {
if let Some(uid) = user.as_deref().and_then(|u| u.parse::<u32>().ok()) {
cmd.uid(uid);
} else if let Some(user) = user {
let (uid, gid) = std::fs::read_to_string("/etc/passwd")
.with_ctx(|_| (ErrorKind::Filesystem, "read /etc/passwd"))?
.lines()
.find_map(|l| {
let mut split = l.trim().split(":");
if user != split.next()? {
return None;
}
split.next(); // throw away x
Some((split.next()?.parse().ok()?, split.next()?.parse().ok()?))
// uid gid
})
.or_not_found(lazy_format!("{user} in /etc/passwd"))?;
cmd.uid(uid);
cmd.gid(gid);
let passwd = std::fs::read_to_string("/etc/passwd")
.with_ctx(|_| (ErrorKind::Filesystem, "read /etc/passwd"));
if passwd.is_err() && user == "root" {
cmd.uid(0);
cmd.gid(0);
} else {
let (uid, gid) = passwd?
.lines()
.find_map(|l| {
let mut split = l.trim().split(":");
if user != split.next()? {
return None;
}
split.next(); // throw away x
Some((split.next()?.parse().ok()?, split.next()?.parse().ok()?))
// uid gid
})
.or_not_found(lazy_format!("{user} in /etc/passwd"))?;
cmd.uid(uid);
cmd.gid(gid);
}
};
if let Some(workdir) = workdir {
cmd.current_dir(workdir);
@@ -134,51 +188,7 @@ pub fn launch(
command,
}: ExecParams,
) -> Result<(), Error> {
if chroot.join("proc/1").exists() {
let ns_id = procfs::process::Process::new_with_root(chroot.join("proc/1"))
.with_ctx(|_| (ErrorKind::Filesystem, "open subcontainer procfs"))?
.namespaces()
.with_ctx(|_| (ErrorKind::Filesystem, "read subcontainer pid 1 ns"))?
.0
.get(OsStr::new("pid"))
.or_not_found("pid namespace")?
.identifier;
for proc in
procfs::process::all_processes().with_ctx(|_| (ErrorKind::Filesystem, "open procfs"))?
{
let proc = proc.with_ctx(|_| (ErrorKind::Filesystem, "read single process details"))?;
let pid = proc.pid();
if proc
.namespaces()
.with_ctx(|_| (ErrorKind::Filesystem, lazy_format!("read pid {} ns", pid)))?
.0
.get(OsStr::new("pid"))
.map_or(false, |ns| ns.identifier == ns_id)
{
let pids = proc.read::<NSPid>("status").with_ctx(|_| {
(
ErrorKind::Filesystem,
lazy_format!("read pid {} NSpid", pid),
)
})?;
if pids.0.len() == 2 && pids.0[1] == 1 {
nix::sys::signal::kill(Pid::from_raw(pid), nix::sys::signal::SIGKILL)
.with_ctx(|_| {
(
ErrorKind::Filesystem,
lazy_format!(
"kill pid {} (determined to be pid 1 in subcontainer)",
pid
),
)
})?;
}
}
}
nix::mount::umount(&chroot.join("proc"))
.with_ctx(|_| (ErrorKind::Filesystem, "unmounting subcontainer procfs"))?;
}
kill_init(Path::new("/proc"), &chroot)?;
if (std::io::stdin().is_terminal()
&& std::io::stdout().is_terminal()
&& std::io::stderr().is_terminal())

View File

@@ -61,30 +61,31 @@ impl StartOSLogger {
use tracing_subscriber::prelude::*;
use tracing_subscriber::{fmt, EnvFilter};
let filter_layer = EnvFilter::builder()
.with_default_directive(
format!("{}=info", std::module_path!().split("::").next().unwrap())
.parse()
.unwrap(),
)
.from_env_lossy();
#[cfg(feature = "unstable")]
let filter_layer = filter_layer
.add_directive("tokio=trace".parse().unwrap())
.add_directive("runtime=trace".parse().unwrap());
let filter_layer = || {
EnvFilter::builder()
.with_default_directive(
format!("{}=info", std::module_path!().split("::").next().unwrap())
.parse()
.unwrap(),
)
.from_env_lossy()
};
let fmt_layer = fmt::layer()
.with_writer(logfile)
.with_line_number(true)
.with_file(true)
.with_target(true);
.with_target(true)
.with_filter(filter_layer());
let sub = tracing_subscriber::registry()
.with(filter_layer)
.with(fmt_layer)
.with(ErrorLayer::default());
let sub = tracing_subscriber::registry();
#[cfg(feature = "unstable")]
let sub = sub.with(console_subscriber::spawn());
#[cfg(not(feature = "unstable"))]
let sub = sub.with(filter_layer());
let sub = sub.with(fmt_layer).with(ErrorLayer::default());
sub
}

View File

@@ -34,8 +34,9 @@ mod v0_3_6_alpha_11;
mod v0_3_6_alpha_12;
mod v0_3_6_alpha_13;
mod v0_3_6_alpha_14;
mod v0_3_6_alpha_15;
pub type Current = v0_3_6_alpha_14::Version; // VERSION_BUMP
pub type Current = v0_3_6_alpha_15::Version; // VERSION_BUMP
impl Current {
#[instrument(skip(self, db))]
@@ -131,7 +132,8 @@ enum Version {
V0_3_6_alpha_11(Wrapper<v0_3_6_alpha_11::Version>),
V0_3_6_alpha_12(Wrapper<v0_3_6_alpha_12::Version>),
V0_3_6_alpha_13(Wrapper<v0_3_6_alpha_13::Version>),
V0_3_6_alpha_14(Wrapper<v0_3_6_alpha_14::Version>), // VERSION_BUMP
V0_3_6_alpha_14(Wrapper<v0_3_6_alpha_14::Version>),
V0_3_6_alpha_15(Wrapper<v0_3_6_alpha_15::Version>), // VERSION_BUMP
Other(exver::Version),
}
@@ -169,7 +171,8 @@ impl Version {
Self::V0_3_6_alpha_11(v) => DynVersion(Box::new(v.0)),
Self::V0_3_6_alpha_12(v) => DynVersion(Box::new(v.0)),
Self::V0_3_6_alpha_13(v) => DynVersion(Box::new(v.0)),
Self::V0_3_6_alpha_14(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP
Self::V0_3_6_alpha_14(v) => DynVersion(Box::new(v.0)),
Self::V0_3_6_alpha_15(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP
Self::Other(v) => {
return Err(Error::new(
eyre!("unknown version {v}"),
@@ -199,7 +202,8 @@ impl Version {
Version::V0_3_6_alpha_11(Wrapper(x)) => x.semver(),
Version::V0_3_6_alpha_12(Wrapper(x)) => x.semver(),
Version::V0_3_6_alpha_13(Wrapper(x)) => x.semver(),
Version::V0_3_6_alpha_14(Wrapper(x)) => x.semver(), // VERSION_BUMP
Version::V0_3_6_alpha_14(Wrapper(x)) => x.semver(),
Version::V0_3_6_alpha_15(Wrapper(x)) => x.semver(), // VERSION_BUMP
Version::Other(x) => x.clone(),
}
}

View File

@@ -0,0 +1,39 @@
use std::collections::BTreeMap;
use exver::{PreReleaseSegment, VersionRange};
use imbl_value::json;
use super::v0_3_5::V0_3_0_COMPAT;
use super::{v0_3_6_alpha_14, VersionT};
use crate::prelude::*;
lazy_static::lazy_static! {
static ref V0_3_6_alpha_15: exver::Version = exver::Version::new(
[0, 3, 6],
[PreReleaseSegment::String("alpha".into()), 15.into()]
);
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Version;
impl VersionT for Version {
type Previous = v0_3_6_alpha_14::Version;
type PreUpRes = ();
async fn pre_up(self) -> Result<Self::PreUpRes, Error> {
Ok(())
}
fn semver(self) -> exver::Version {
V0_3_6_alpha_15.clone()
}
fn compat(self) -> &'static VersionRange {
&V0_3_0_COMPAT
}
fn up(self, db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> {
Ok(())
}
fn down(self, _db: &mut Value) -> Result<(), Error> {
Ok(())
}
}