Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0655bdc
Centralise vsock frame proto
isbm Aug 1, 2026
e5dfb11
Add centralised vsock proto UT
isbm Aug 1, 2026
2122221
Validate guest working directories and workspace paths
isbm Aug 1, 2026
c238907
Add workspace unit tests
isbm Aug 1, 2026
52aff66
Lints
isbm Aug 1, 2026
2caa623
Define the versioned remote operation proto
isbm Aug 1, 2026
537bddb
Add remote operation proto UT
isbm Aug 1, 2026
4b2ace8
Add remote auth and backend interfaces
isbm Aug 2, 2026
ffecebc
Add Unit tests for the remote auth
isbm Aug 2, 2026
e2c0813
Add fake remote backend and tests
isbm Aug 2, 2026
bc1f3a8
Add concurrent backend streaming
isbm Aug 2, 2026
d36d567
Add unit tests for loopback remote harness
isbm Aug 2, 2026
b9c97f9
Implement remote loopback harness
isbm Aug 2, 2026
a32c90d
Fix regression: TUI rendering on a wrong channel before the first boo…
isbm Aug 2, 2026
d0080db
Add UT for TUI rendering
isbm Aug 2, 2026
93eb8e7
ADd unit tests for strict remote env and configurable policies
isbm Aug 2, 2026
8c866fd
Add strict remote env and configurable policies
isbm Aug 2, 2026
07f5477
Add remote build policy
isbm Aug 3, 2026
04b1c3c
Add remote build policy unit tests
isbm Aug 3, 2026
932dbda
Implement transparency wrapper on one build command
isbm Aug 3, 2026
e589956
Update images
isbm Aug 3, 2026
77e050b
Add unit tests for transparency layer
isbm Aug 3, 2026
7321c3c
Implement shared remote-make ownership and diagnostic sync cleanup wi…
isbm Aug 3, 2026
4e2d7ae
Add regression tests for installers, cleanup, bounds, and preserved b…
isbm Aug 3, 2026
842f420
Linters
isbm Aug 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
sha2 = "0.10"
tokio = { version = "1", features = ["rt-multi-thread", "net", "macros", "sync", "io-util", "process"] }
tokio = { version = "1", features = ["rt-multi-thread", "net", "macros", "sync", "io-util", "process", "time"] }
tokio-vsock = "0.7"
ratatui = "0.30"
crossterm = "0.28"
Expand Down
10 changes: 5 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -43,29 +43,29 @@ ensure-toolchain:

dev: ensure-toolchain
cargo build --bin bunkerbox --bin bunkerbox-image
cargo build --bin bunkerbox-netrelay --target $(VSCOMM_TARGET)
cargo build --bin bunkerbox-vscomm --target $(VSCOMM_TARGET)
cargo build --bin bunkerbox-netrelay --bin bunkerbox-vscomm --bin bunkerbox-remote --target $(VSCOMM_TARGET)
cargo build --bin bunkerbox-status --target $(VSCOMM_TARGET)
rm -rf target/dist
mkdir -p target/dist
cp target/debug/bunkerbox target/dist/
cp target/debug/bunkerbox-image target/dist/
cp target/$(VSCOMM_TARGET)/debug/bunkerbox-netrelay target/dist/
cp target/$(VSCOMM_TARGET)/debug/bunkerbox-vscomm target/dist/
cp target/$(VSCOMM_TARGET)/debug/bunkerbox-remote target/dist/
cp target/$(VSCOMM_TARGET)/debug/bunkerbox-status target/dist/
cp target/$(VSCOMM_TARGET)/debug/bunkerbox-netrelay target/debug/bunkerbox-netrelay

release: ensure-toolchain
cargo build --bin bunkerbox --bin bunkerbox-image --release
cargo build --bin bunkerbox-netrelay --target $(VSCOMM_TARGET) --release
cargo build --bin bunkerbox-vscomm --target $(VSCOMM_TARGET) --release
cargo build --bin bunkerbox-netrelay --bin bunkerbox-vscomm --bin bunkerbox-remote --target $(VSCOMM_TARGET) --release
cargo build --bin bunkerbox-status --target $(VSCOMM_TARGET) --release
rm -rf target/dist
mkdir -p target/dist
cp target/release/bunkerbox target/dist/
cp target/release/bunkerbox-image target/dist/
cp target/$(VSCOMM_TARGET)/release/bunkerbox-netrelay target/dist/
cp target/$(VSCOMM_TARGET)/release/bunkerbox-vscomm target/dist/
cp target/$(VSCOMM_TARGET)/release/bunkerbox-remote target/dist/
cp target/$(VSCOMM_TARGET)/release/bunkerbox-status target/dist/
cp target/$(VSCOMM_TARGET)/release/bunkerbox-netrelay target/release/bunkerbox-netrelay

Expand All @@ -83,7 +83,7 @@ setup: dev
target/debug/bunkerbox setup

musl-vscomm: ensure-toolchain
cargo build --bin bunkerbox-vscomm --target $(VSCOMM_TARGET)
cargo build --bin bunkerbox-vscomm --bin bunkerbox-remote --target $(VSCOMM_TARGET)
cargo build --bin bunkerbox-status --target $(VSCOMM_TARGET)

image: dev
Expand Down
1 change: 1 addition & 0 deletions images/crush.conf
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ containerfile: |
RUN chmod 0755 /usr/local/bin/bunker-entrypoint

COPY bunkerbox-vscomm /usr/local/bunkerbox/bin/bunkerbox-vscomm
COPY bunkerbox-remote /usr/local/bunkerbox/bin/bunkerbox-remote
COPY bunkerbox-status /usr/local/bunkerbox/bin/bunkerbox-status

ENV HOME=/home/bunkerbox \
Expand Down
1 change: 1 addition & 0 deletions images/kilocode.conf
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ containerfile: |
RUN chmod 0755 /usr/local/bin/bunker-entrypoint

COPY bunkerbox-vscomm /usr/local/bunkerbox/bin/bunkerbox-vscomm
COPY bunkerbox-remote /usr/local/bunkerbox/bin/bunkerbox-remote

COPY bunkerbox-status /usr/local/bunkerbox/bin/bunkerbox-status

Expand Down
1 change: 1 addition & 0 deletions images/opencode.conf
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ containerfile: |
RUN chmod 0755 /usr/local/bin/bunker-entrypoint

COPY bunkerbox-vscomm /usr/local/bunkerbox/bin/bunkerbox-vscomm
COPY bunkerbox-remote /usr/local/bunkerbox/bin/bunkerbox-remote
COPY bunkerbox-status /usr/local/bunkerbox/bin/bunkerbox-status

ENV HOME=/home/bunkerbox \
Expand Down
29 changes: 27 additions & 2 deletions src/bin/bunkerbox-image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,10 +226,13 @@ run_app() {{
}}

VSCOMM_BIN="/usr/local/bunkerbox/bin"
if [ -x "$VSCOMM_BIN/bunkerbox-remote" ]; then
"$VSCOMM_BIN/bunkerbox-remote" install
fi
if [ -x "$VSCOMM_BIN/bunkerbox-vscomm" ]; then
"$VSCOMM_BIN/bunkerbox-vscomm" install
export PATH="$VSCOMM_BIN:$PATH"
fi
export PATH="$VSCOMM_BIN:$PATH"

if ! command -v bunkerbox-status >/dev/null 2>&1; then
bunkerbox-status() {{ :; }}
Expand Down Expand Up @@ -286,6 +289,11 @@ fn write_build_context(config: &ImageConfig, build_dir: &Path) -> Result<(), Str
fs::copy(&vscomm_path, &dest).map_err(|err| format!("failed to copy vscomm binary {}: {err}", dest.display()))?;
fs::set_permissions(&dest, fs::Permissions::from_mode(0o755)).map_err(|err| format!("failed to chmod {}: {err}", dest.display()))?;

let remote_path = find_remote_binary()?;
let dest = build_dir.join("bunkerbox-remote");
fs::copy(&remote_path, &dest).map_err(|err| format!("failed to copy remote binary {}: {err}", dest.display()))?;
fs::set_permissions(&dest, fs::Permissions::from_mode(0o755)).map_err(|err| format!("failed to chmod {}: {err}", dest.display()))?;

let status_path = find_status_binary()?;
let dest = build_dir.join("bunkerbox-status");
fs::copy(&status_path, &dest).map_err(|err| format!("failed to copy status binary {}: {err}", dest.display()))?;
Expand All @@ -295,7 +303,11 @@ fn write_build_context(config: &ImageConfig, build_dir: &Path) -> Result<(), Str
if file.path.is_absolute() || file.path.components().any(|part| matches!(part, std::path::Component::ParentDir)) {
return Err(format!("unsafe build file path: {}", file.path.display()));
}
if file.path == Path::new("bunker-entrypoint") || file.path == Path::new("bunkerbox-vscomm") || file.path == Path::new("bunkerbox-status") {
if file.path == Path::new("bunker-entrypoint")
|| file.path == Path::new("bunkerbox-vscomm")
|| file.path == Path::new("bunkerbox-status")
|| file.path == Path::new("bunkerbox-remote")
{
return Err(format!("image config files must not override reserved file: {}", file.path.display()));
}

Expand Down Expand Up @@ -336,6 +348,15 @@ fn find_status_binary() -> Result<PathBuf, String> {
}
}

fn find_remote_binary() -> Result<PathBuf, String> {
let path = dist_dir()?.join("bunkerbox-remote");
if path.is_file() {
Ok(path)
} else {
Err("bunkerbox-remote not found in target/dist/. Run: make dev".into())
}
}

fn podman_build(config: &ImageConfig, build_dir: &Path) -> Result<(), String> {
let mut args = vec!["build".to_string(), "--no-cache".to_string()];

Expand Down Expand Up @@ -451,3 +472,7 @@ where
}
}
}

#[cfg(test)]
#[path = "../bunkerbox-image_ut.rs"]
mod tests;
205 changes: 205 additions & 0 deletions src/bin/bunkerbox-remote.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
use bunkerbox::guest_install::install_remote_make_link;
#[cfg(test)]
use bunkerbox::remote::RemoteSnapshotId;
use bunkerbox::remote_client::{
execute_remote_request_to, logical_workspace_cwd, new_request_id, remote_build_request, remote_diagnostic_sync_request, remote_environment_names,
remote_session_from_env, remote_sync_request, remote_tool_enabled, selected_remote_environment, RemoteCompletion,
};
#[cfg(test)]
use bunkerbox::vscomm::RequestId;
use bunkerbox::vscomm::{RemoteRequest, WorkspaceSessionId, TOOLCHAIN_PORT, VSCOMM_BIN_DIR};
use std::env;
use std::fs;
use std::io::{self, Read, Write};
use std::mem;
use std::path::Path;

const HOST_CID: u32 = 2;

#[derive(Debug, PartialEq, Eq)]
enum RemoteCommand {
Sync,
Build { tool: String, args: Vec<String> },
}

fn main() {
match run() {
Ok(code) => std::process::exit(code),
Err(error) => {
eprintln!("bunkerbox-remote: remote operation failed: {error}");
std::process::exit(1);
}
}
}

fn run() -> Result<i32, String> {
let invoked_as =
env::args_os().next().and_then(|value| Path::new(&value).file_name().and_then(|name| name.to_str()).map(str::to_owned)).unwrap_or_default();
let args = env::args().skip(1).collect::<Vec<_>>();

if invoked_as == "make" {
return run_transparent_make(&args);
}
if invoked_as != "bunkerbox-remote" {
return Err("bunkerbox-remote must be invoked directly or through the managed make symlink".to_string());
}
if args.len() == 1 && args[0] == "install" {
install_remote_links()?;
return Ok(0);
}

run_explicit(&args)
}

fn run_explicit(args: &[String]) -> Result<i32, String> {
let command = parse_command(args)?;
match &command {
RemoteCommand::Sync => eprintln!("bunkerbox-remote: syncing"),
RemoteCommand::Build { tool, .. } => eprintln!("bunkerbox-remote: building {tool}"),
}

let cwd = logical_workspace_cwd(&env::current_dir().map_err(|error| format!("current directory: {error}"))?)?;
let session = remote_session_from_env()?;
match command {
RemoteCommand::Sync => {
sync_snapshot(session)?;
Ok(0)
}
RemoteCommand::Build { tool, args } => run_build_with_sync(cwd, tool, args, session),
}
}

fn run_transparent_make(args: &[String]) -> Result<i32, String> {
let cwd = logical_workspace_cwd(&env::current_dir().map_err(|error| format!("current directory: {error}"))?)?;
let session = remote_session_from_env()?;
run_build_with_sync(cwd, "make".to_string(), args.to_vec(), session)
}

fn run_build_with_sync(cwd: String, tool: String, args: Vec<String>, session: WorkspaceSessionId) -> Result<i32, String> {
let environment = selected_remote_environment(remote_environment_names());
run_build_with_sync_using(cwd, tool, args, environment, session, execute_request_over_vsock)
}

fn run_build_with_sync_using<F>(
cwd: String, tool: String, args: Vec<String>, environment: Vec<(String, String)>, session: WorkspaceSessionId, mut execute: F,
) -> Result<i32, String>
where
F: FnMut(RemoteRequest) -> Result<RemoteCompletion, String>,
{
let snapshot_id = match execute(remote_sync_request(new_request_id(), session))? {
RemoteCompletion::Synced(snapshot_id) => snapshot_id,
RemoteCompletion::Completed(_) => return Err("remote sync returned a build completion".to_string()),
};
let request = remote_build_request(new_request_id(), session, cwd, tool, args, environment, snapshot_id)?;
match execute(request)? {
RemoteCompletion::Completed(code) => Ok(code),
RemoteCompletion::Synced(_) => Err("remote build returned a sync completion".to_string()),
}
}

fn execute_request_over_vsock(request: RemoteRequest) -> Result<RemoteCompletion, String> {
let mut stream = connect_toolchain()?;
execute_remote_request_to(&mut stream, request, &mut io::stdout(), &mut io::stderr())
}

fn sync_snapshot(session: WorkspaceSessionId) -> Result<(), String> {
sync_snapshot_using(session, execute_request_over_vsock)
}

fn sync_snapshot_using<F>(session: WorkspaceSessionId, mut execute: F) -> Result<(), String>
where
F: FnMut(RemoteRequest) -> Result<RemoteCompletion, String>,
{
match execute(remote_diagnostic_sync_request(new_request_id(), session))? {
RemoteCompletion::Synced(_) => Err("diagnostic sync returned a retained capability".to_string()),
RemoteCompletion::Completed(0) => Ok(()),
RemoteCompletion::Completed(code) => Err(format!("remote sync returned exit code {code}")),
}
}

fn parse_command(args: &[String]) -> Result<RemoteCommand, String> {
match args {
[command] if command == "sync" => Ok(RemoteCommand::Sync),
[command, tool, rest @ ..] if command == "build" && !tool.is_empty() => Ok(RemoteCommand::Build { tool: tool.clone(), args: rest.to_vec() }),
[command, ..] if command == "build" => Err("usage: bunkerbox-remote build <tool> [args...]".to_string()),
[] => Err("usage: bunkerbox-remote sync | build <tool> [args...]".to_string()),
_ => Err("usage: bunkerbox-remote sync | build <tool> [args...]".to_string()),
}
}

#[cfg(test)]
fn build_request(
command: RemoteCommand, cwd: String, request_id: RequestId, session_id: WorkspaceSessionId, snapshot_id: RemoteSnapshotId,
) -> Result<RemoteRequest, String> {
match command {
RemoteCommand::Sync => Ok(remote_sync_request(request_id, session_id)),
RemoteCommand::Build { tool, args } => remote_build_request(request_id, session_id, cwd, tool, args, Vec::new(), snapshot_id),
}
}

fn install_remote_links() -> Result<(), String> {
fs::create_dir_all(VSCOMM_BIN_DIR).map_err(|error| format!("mkdir {VSCOMM_BIN_DIR}: {error}"))?;
let executable = env::current_exe().map_err(|error| format!("failed to locate remote binary: {error}"))?;
install_remote_make_link(Path::new(VSCOMM_BIN_DIR), &executable, remote_tool_enabled("make"))
}

fn connect_toolchain() -> Result<VsockStream, String> {
vsock_connect(HOST_CID, TOOLCHAIN_PORT).map_err(|error| format!("toolchain vsock connect: {error}"))
}

fn vsock_connect(cid: u32, port: u32) -> io::Result<VsockStream> {
unsafe {
let fd = libc::socket(libc::AF_VSOCK, libc::SOCK_STREAM, 0);
if fd < 0 {
return Err(io::Error::last_os_error());
}

let addr = libc::sockaddr_vm { svm_family: libc::AF_VSOCK as u16, svm_reserved1: 0, svm_port: port, svm_cid: cid, svm_zero: [0u8; 4] };
let addr_ptr = &addr as *const libc::sockaddr_vm as *const libc::sockaddr;
let addr_len = mem::size_of::<libc::sockaddr_vm>() as libc::socklen_t;
if libc::connect(fd, addr_ptr, addr_len) < 0 {
let error = io::Error::last_os_error();
libc::close(fd);
return Err(error);
}
Ok(VsockStream { fd })
}
}

struct VsockStream {
fd: libc::c_int,
}

impl Read for VsockStream {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let result = unsafe { libc::read(self.fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) };
if result < 0 {
return Err(io::Error::last_os_error());
}
Ok(result as usize)
}
}

impl Write for VsockStream {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let result = unsafe { libc::write(self.fd, buf.as_ptr() as *const libc::c_void, buf.len()) };
if result < 0 {
return Err(io::Error::last_os_error());
}
Ok(result as usize)
}

fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}

impl Drop for VsockStream {
fn drop(&mut self) {
unsafe { libc::close(self.fd) };
}
}

#[cfg(test)]
#[path = "../bunkerbox-remote_ut.rs"]
mod tests;
2 changes: 2 additions & 0 deletions src/bin/bunkerbox-status.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#[path = "../remote.rs"]
mod remote;
#[path = "../vscomm/mod.rs"]
mod vscomm;

Expand Down
Loading
Loading