mirror of
https://github.com/Noratrieb/captain.git
synced 2026-01-16 15:25:02 +01:00
stuff
This commit is contained in:
parent
f0ffa77500
commit
e82f2076b8
10 changed files with 329 additions and 5 deletions
104
Cargo.lock
generated
104
Cargo.lock
generated
|
|
@ -131,8 +131,15 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"clap",
|
||||
"eyre",
|
||||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.9"
|
||||
|
|
@ -153,6 +160,12 @@ dependencies = [
|
|||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
|
|
@ -165,6 +178,16 @@ version = "0.3.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
|
|
@ -253,6 +276,8 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"eyre",
|
||||
"rustix",
|
||||
"serde",
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
|
@ -323,6 +348,35 @@ dependencies = [
|
|||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.210"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.210"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
|
|
@ -361,6 +415,13 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tcpecho"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"eyre",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.8"
|
||||
|
|
@ -371,6 +432,40 @@ dependencies = [
|
|||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.40"
|
||||
|
|
@ -544,3 +639,12 @@ name = "windows_x86_64_msvc"
|
|||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.6.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[workspace]
|
||||
resolver = "2"
|
||||
members = [ "cog", "coreutils","quarterdeck"]
|
||||
members = [ "cog", "coreutils","quarterdeck", "test-services/tcpecho"]
|
||||
|
||||
[workspace.dependencies]
|
||||
eyre = "0.6.12"
|
||||
|
|
|
|||
|
|
@ -6,9 +6,8 @@ cd "$(dirname "$0")"
|
|||
|
||||
target_tuple="$(uname -m)-unknown-linux-musl"
|
||||
|
||||
cargo build -p quarterdeck --target "$target_tuple"
|
||||
cargo build -p cog --target "$target_tuple"
|
||||
cargo build -p coreutils --target "$target_tuple"
|
||||
cargo build --target "$target_tuple" \
|
||||
-p quarterdeck -p cog -p coreutils -p tcpecho
|
||||
|
||||
target_dir="../target/$target_tuple/debug"
|
||||
|
||||
|
|
@ -25,6 +24,15 @@ install_bin quarterdeck
|
|||
install_bin cog
|
||||
install_bin net
|
||||
install_bin ls
|
||||
install_bin user
|
||||
install_bin tcpecho
|
||||
|
||||
mkdir -p rootfs/etc
|
||||
cp /etc/resolv.conf rootfs/etc/resolv.conf
|
||||
|
||||
mkdir -p rootfs/etc/services
|
||||
|
||||
cat > rootfs/etc/services/tcpecho2000.toml <<EOF
|
||||
name = "tcpecho2000"
|
||||
exec = [ "/bin/tcpecho", "0.0.0.0:2000" ]
|
||||
EOF
|
||||
|
|
|
|||
|
|
@ -10,4 +10,6 @@ exec bwrap \
|
|||
--unshare-uts \
|
||||
--unshare-cgroup \
|
||||
--as-pid-1 \
|
||||
--uid 1 \
|
||||
--gid 1 \
|
||||
/bin/quarterdeck
|
||||
|
|
|
|||
|
|
@ -6,3 +6,4 @@ edition = "2021"
|
|||
[dependencies]
|
||||
clap = { version = "4.5.17", features = ["derive"] }
|
||||
eyre.workspace = true
|
||||
rustix = { version = "0.38.37", features = ["process"] }
|
||||
|
|
|
|||
26
coreutils/src/bin/user.rs
Normal file
26
coreutils/src/bin/user.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
use clap::Parser;
|
||||
use eyre::Result;
|
||||
|
||||
#[derive(Parser)]
|
||||
struct Cmd {
|
||||
#[command(subcommand)]
|
||||
cmd: Subcommand,
|
||||
}
|
||||
|
||||
#[derive(clap::Subcommand)]
|
||||
enum Subcommand {
|
||||
/// Get the current user's uid
|
||||
Id,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Cmd::parse();
|
||||
|
||||
match args.cmd {
|
||||
Subcommand::Id => {
|
||||
println!("{}", rustix::process::getuid().as_raw());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -6,5 +6,7 @@ edition = "2021"
|
|||
[dependencies]
|
||||
eyre.workspace = true
|
||||
rustix = { version = "0.38.37", features = ["process", "thread"] }
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
toml = "0.8.19"
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,13 @@
|
|||
use eyre::Result;
|
||||
use std::{
|
||||
fs::{DirEntry, File},
|
||||
path::Path,
|
||||
process::Stdio,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use eyre::{bail, eyre, Context, Result};
|
||||
use rustix::{io::Errno, process::WaitOptions};
|
||||
use serde::Deserialize;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
fn main() {
|
||||
|
|
@ -17,6 +26,31 @@ fn run() -> Result<()> {
|
|||
warn!(?err, "Failed to set PR_SET_NO_NEW_PRIVS");
|
||||
}
|
||||
|
||||
let result = std::thread::Builder::new()
|
||||
.name("reaper".to_owned())
|
||||
.spawn(|| reaper());
|
||||
if let Err(err) = result {
|
||||
error!(?err, "Failed to spawn reaper thread");
|
||||
}
|
||||
|
||||
let services = read_services(Path::new("/etc/services"));
|
||||
match services {
|
||||
Ok((services, errors)) => {
|
||||
for error in errors {
|
||||
error!(?error, "Failed to read service");
|
||||
}
|
||||
|
||||
for service in services {
|
||||
std::thread::spawn(move || {
|
||||
if let Err(err) = run_service(&service) {
|
||||
error!(name = %service.name, ?err, "Failed to spawn service");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Err(err) => error!(?err, "Failed to read services"),
|
||||
}
|
||||
|
||||
std::process::Command::new("/bin/cog")
|
||||
.env("PATH", "/bin")
|
||||
.spawn()?
|
||||
|
|
@ -24,3 +58,108 @@ fn run() -> Result<()> {
|
|||
|
||||
loop {}
|
||||
}
|
||||
|
||||
fn reaper() {
|
||||
loop {
|
||||
let result = rustix::process::waitpid(None, WaitOptions::empty());
|
||||
if let Err(err) = result {
|
||||
if err == Errno::CHILD {
|
||||
// No children.. maybe we're gonna get children in the future?
|
||||
// TODO: this is probably unnecessary if we do pid 1 properly?
|
||||
std::thread::sleep(Duration::from_secs(1));
|
||||
continue;
|
||||
}
|
||||
error!(?err, "Failed to waitpid");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Service {
|
||||
name: String,
|
||||
exec: Vec<String>,
|
||||
}
|
||||
|
||||
fn run_service(service: &Service) -> Result<()> {
|
||||
info!(name = %service.name, "Starting service");
|
||||
|
||||
std::fs::create_dir_all("/var/logs").wrap_err("creating log base directory")?;
|
||||
let logs = File::options()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(Path::new("/var/logs").join(&service.name))
|
||||
.wrap_err("opening log file")?;
|
||||
|
||||
let mut backoff_ms = 1;
|
||||
loop {
|
||||
let mut cmd = std::process::Command::new(&service.exec[0]);
|
||||
cmd.args(&service.exec[1..]);
|
||||
|
||||
cmd.stdin(Stdio::null());
|
||||
cmd.stdout(Stdio::from(logs.try_clone().wrap_err("duping log file")?));
|
||||
cmd.stderr(Stdio::from(logs.try_clone().wrap_err("duping log file")?));
|
||||
|
||||
let mut child = cmd
|
||||
.spawn()
|
||||
.wrap_err_with(|| format!("spawning command {}", service.exec[0]))?;
|
||||
|
||||
let result = child.wait().wrap_err("waiting for child failed")?;
|
||||
if result.success() {
|
||||
info!(name = %service.name, "Service finished, restarting in {backoff_ms}ms");
|
||||
} else {
|
||||
info!(name = %service.name, code = %result.code().unwrap_or(-1), "Service errored, restarting in {backoff_ms}ms");
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(backoff_ms));
|
||||
backoff_ms *= 10;
|
||||
}
|
||||
}
|
||||
|
||||
fn read_services(path: &Path) -> Result<(Vec<Service>, Vec<eyre::Report>)> {
|
||||
let service_files =
|
||||
std::fs::read_dir(path).wrap_err_with(|| format!("reading {}", path.display()))?;
|
||||
|
||||
let mut services: Vec<Service> = vec![];
|
||||
let mut errors = vec![];
|
||||
|
||||
for service_file in service_files {
|
||||
match read_service_file(service_file) {
|
||||
Ok(service) => {
|
||||
if services
|
||||
.iter()
|
||||
.any(|old_service| old_service.name == service.name)
|
||||
{
|
||||
errors.push(eyre!("service name found twice: {}", service.name))
|
||||
} else {
|
||||
services.push(service)
|
||||
}
|
||||
}
|
||||
Err(err) => errors.push(err),
|
||||
}
|
||||
}
|
||||
|
||||
Ok((services, errors))
|
||||
}
|
||||
|
||||
fn read_service_file(service_file: std::io::Result<DirEntry>) -> Result<Service> {
|
||||
let service_file = service_file.wrap_err("failed to read services")?.path();
|
||||
let service_config = std::fs::read(&service_file)
|
||||
.wrap_err_with(|| format!("reading {}", service_file.display()))?;
|
||||
let service_config = std::str::from_utf8(&service_config).wrap_err_with(|| {
|
||||
format!(
|
||||
"service config for {} is invalid UTF-8",
|
||||
service_file.display()
|
||||
)
|
||||
})?;
|
||||
let service = toml::from_str::<Service>(service_config)
|
||||
.wrap_err_with(|| format!("service config for {} is invalid", service_file.display()))?;
|
||||
|
||||
if service.exec.len() < 1 {
|
||||
bail!(
|
||||
"service config for {} is invalid, must contain at least one element in exec",
|
||||
service_file.display()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(service)
|
||||
}
|
||||
|
|
|
|||
7
test-services/tcpecho/Cargo.toml
Normal file
7
test-services/tcpecho/Cargo.toml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[package]
|
||||
name = "tcpecho"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
eyre.workspace = true
|
||||
35
test-services/tcpecho/src/main.rs
Normal file
35
test-services/tcpecho/src/main.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
use std::{
|
||||
io::{Read, Write},
|
||||
net::{TcpListener, TcpStream},
|
||||
};
|
||||
|
||||
use eyre::{Context, Result};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let addr = std::env::var("BIND_ADDR")
|
||||
.ok()
|
||||
.or(std::env::args().nth(1))
|
||||
.unwrap_or("127.0.0.1:1000".to_owned());
|
||||
println!("Listening on address {addr}");
|
||||
let stream = TcpListener::bind(&addr).wrap_err_with(|| format!("binding socket on {addr}"))?;
|
||||
|
||||
loop {
|
||||
let next = stream.accept()?;
|
||||
std::thread::spawn(move || {
|
||||
if let Err(err) = echo(next.0) {
|
||||
eprintln!("{err:?}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn echo(mut stream: TcpStream) -> Result<()> {
|
||||
let mut buf = [0; 1024];
|
||||
loop {
|
||||
let read = stream.read(&mut buf)?;
|
||||
if read == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
stream.write_all(&buf[..read])?;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue