mirror of
https://github.com/Noratrieb/captain.git
synced 2026-01-15 23:05: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 = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"eyre",
|
"eyre",
|
||||||
|
"rustix",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
|
@ -153,6 +160,12 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.14.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
|
@ -165,6 +178,16 @@ version = "0.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
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]]
|
[[package]]
|
||||||
name = "is_terminal_polyfill"
|
name = "is_terminal_polyfill"
|
||||||
version = "1.70.1"
|
version = "1.70.1"
|
||||||
|
|
@ -253,6 +276,8 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"eyre",
|
"eyre",
|
||||||
"rustix",
|
"rustix",
|
||||||
|
"serde",
|
||||||
|
"toml",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
@ -323,6 +348,35 @@ dependencies = [
|
||||||
"windows-sys",
|
"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]]
|
[[package]]
|
||||||
name = "sharded-slab"
|
name = "sharded-slab"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
|
|
@ -361,6 +415,13 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tcpecho"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"eyre",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "thread_local"
|
||||||
version = "1.1.8"
|
version = "1.1.8"
|
||||||
|
|
@ -371,6 +432,40 @@ dependencies = [
|
||||||
"once_cell",
|
"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]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.40"
|
version = "0.1.40"
|
||||||
|
|
@ -544,3 +639,12 @@ name = "windows_x86_64_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
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]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [ "cog", "coreutils","quarterdeck"]
|
members = [ "cog", "coreutils","quarterdeck", "test-services/tcpecho"]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
eyre = "0.6.12"
|
eyre = "0.6.12"
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,8 @@ cd "$(dirname "$0")"
|
||||||
|
|
||||||
target_tuple="$(uname -m)-unknown-linux-musl"
|
target_tuple="$(uname -m)-unknown-linux-musl"
|
||||||
|
|
||||||
cargo build -p quarterdeck --target "$target_tuple"
|
cargo build --target "$target_tuple" \
|
||||||
cargo build -p cog --target "$target_tuple"
|
-p quarterdeck -p cog -p coreutils -p tcpecho
|
||||||
cargo build -p coreutils --target "$target_tuple"
|
|
||||||
|
|
||||||
target_dir="../target/$target_tuple/debug"
|
target_dir="../target/$target_tuple/debug"
|
||||||
|
|
||||||
|
|
@ -25,6 +24,15 @@ install_bin quarterdeck
|
||||||
install_bin cog
|
install_bin cog
|
||||||
install_bin net
|
install_bin net
|
||||||
install_bin ls
|
install_bin ls
|
||||||
|
install_bin user
|
||||||
|
install_bin tcpecho
|
||||||
|
|
||||||
mkdir -p rootfs/etc
|
mkdir -p rootfs/etc
|
||||||
cp /etc/resolv.conf rootfs/etc/resolv.conf
|
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-uts \
|
||||||
--unshare-cgroup \
|
--unshare-cgroup \
|
||||||
--as-pid-1 \
|
--as-pid-1 \
|
||||||
|
--uid 1 \
|
||||||
|
--gid 1 \
|
||||||
/bin/quarterdeck
|
/bin/quarterdeck
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,4 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.5.17", features = ["derive"] }
|
clap = { version = "4.5.17", features = ["derive"] }
|
||||||
eyre.workspace = true
|
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]
|
[dependencies]
|
||||||
eyre.workspace = true
|
eyre.workspace = true
|
||||||
rustix = { version = "0.38.37", features = ["process", "thread"] }
|
rustix = { version = "0.38.37", features = ["process", "thread"] }
|
||||||
|
serde = { version = "1.0.210", features = ["derive"] }
|
||||||
|
toml = "0.8.19"
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
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};
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
@ -17,6 +26,31 @@ fn run() -> Result<()> {
|
||||||
warn!(?err, "Failed to set PR_SET_NO_NEW_PRIVS");
|
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")
|
std::process::Command::new("/bin/cog")
|
||||||
.env("PATH", "/bin")
|
.env("PATH", "/bin")
|
||||||
.spawn()?
|
.spawn()?
|
||||||
|
|
@ -24,3 +58,108 @@ fn run() -> Result<()> {
|
||||||
|
|
||||||
loop {}
|
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