Skip to content
This repository was archived by the owner on Feb 23, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
- [x] Generate faucet and bootstrap accounts
- [x] Build genesis
- [ ] Docker Build
- [ ] Build Bootstrap Image
- [x] Build Bootstrap Image
- [ ] Push Image to registry
- [ ] Create & Deploy Secrets
- [ ] Bootstrap
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ cargo run --bin cluster --
--release-channel <agave-version: e.g. v1.17.28> # note: MUST include the "v"
```

#### Build from Local Repo and Configure Genesis
#### Build from Local Repo and Configure Genesis and Bootstrap Validator Image
Example:
```
cargo run --bin cluster --
Expand All @@ -51,4 +51,9 @@ cargo run --bin cluster --
--max-genesis-archive-unpacked-size <size in bytes>
--target-lamports-per-signature <lamports-per-signature>
--slots-per-epoch <slots-per-epoch>
# docker config
--registry <docker-registry> # e.g. gregcusack
--tag <docker-image-tag> # e.g. v1
--base-image <base-image> # e.g. ubuntu:20.04
--image-name <docker-image-name> # e.g. cluster-image
```
190 changes: 190 additions & 0 deletions src/docker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
use {
crate::{new_spinner_progress_bar, release::DeployMethod, ValidatorType, BUILD},
log::*,
std::{
env,
error::Error,
fs,
path::{Path, PathBuf},
process::{Command, Output, Stdio},
},
};

pub struct DockerConfig {
pub base_image: String,
pub image_name: String,
pub tag: String,
pub registry: String,
deploy_method: DeployMethod,
}

impl DockerConfig {
pub fn new(
base_image: String,
image_name: String,
tag: String,
registry: String,
deploy_method: DeployMethod,
) -> Self {
DockerConfig {
base_image,
image_name,
tag,
registry,
deploy_method,
}
}

pub fn build_image(
&self,
solana_root_path: &Path,
validator_type: &ValidatorType,
) -> Result<(), Box<dyn Error>> {
match validator_type {
ValidatorType::Bootstrap => (),
ValidatorType::Standard | ValidatorType::RPC | ValidatorType::Client => {
return Err(format!(
"Build docker image for validator type: {validator_type} not supported yet"
)
.into());
}
}
let image_name = format!("{validator_type}-{}", self.image_name);
let docker_path = solana_root_path.join(format!("docker-build/{validator_type}"));
match self.create_base_image(solana_root_path, image_name, &docker_path, validator_type) {
Ok(res) => {
if res.status.success() {
info!("Successfully created base Image");
Ok(())
} else {
error!("Failed to build base image");
Err(String::from_utf8_lossy(&res.stderr).into())
}
}
Err(err) => Err(err),
}
}

fn create_base_image(
&self,
solana_root_path: &Path,
image_name: String,
docker_path: &PathBuf,
validator_type: &ValidatorType,
) -> Result<Output, Box<dyn Error>> {
self.create_dockerfile(validator_type, docker_path, None)?;

trace!("Tmp: {}", docker_path.as_path().display());
trace!("Exists: {}", docker_path.as_path().exists());

// We use std::process::Command here because Docker-rs is very slow building dockerfiles
// when they are in large repos. Docker-rs doesn't seem to support the `--file` flag natively.
// so we result to using std::process::Command
let dockerfile = docker_path.join("Dockerfile");
let context_path = solana_root_path.display().to_string();

let progress_bar = new_spinner_progress_bar();
progress_bar.set_message(format!("{BUILD}Building {validator_type} docker image...",));

let command = format!(
"docker build -t {}/{image_name}:{} -f {dockerfile:?} {context_path}",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question on using {dockerfile:?} here: the documentation says to use the Debug formatter if you want to escape the path at https://doc.rust-lang.org/std/path/struct.PathBuf.html#method.display

Is using display() no good for your use-case?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did not realize that. will update.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that you're using the _lossy bits for Windows, and display "This may perform lossy conversion, depending on the platform", I'm more convinced that it's the better approach

self.registry, self.tag,
);

let output = match Command::new("sh")
.arg("-c")
.arg(&command)
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("Failed to execute command")
.wait_with_output()
{
Ok(res) => Ok(res),
Err(err) => Err(Box::new(err) as Box<dyn Error>),
};
progress_bar.finish_and_clear();
info!("{validator_type} image build complete");

output
}

fn copy_file_to_docker(
source_dir: &Path,
docker_dir: &Path,
file_name: &str,
) -> std::io::Result<()> {
let source_path = source_dir.join("src/scripts").join(file_name);
let destination_path = docker_dir.join(file_name);
fs::copy(source_path, destination_path)?;
Ok(())
}

fn create_dockerfile(
&self,
validator_type: &ValidatorType,
docker_path: &PathBuf,
content: Option<&str>,
) -> Result<(), Box<dyn Error>> {
if docker_path.exists() {
fs::remove_dir_all(docker_path)?;
}
fs::create_dir_all(docker_path)?;

if let DeployMethod::Local(_) = self.deploy_method {
if validator_type == &ValidatorType::Bootstrap {
let manifest_path =
PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("$CARGO_MANIFEST_DIR"));
Comment on lines +136 to +137
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just did a little test, and CARGO_MANIFEST_DIR is only set when invoking the program using cargo, and not just calling the executable directly.

Since this might be slightly brittle, would it make sense to also pass a path to the scripts directory?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the best way to do this? Just add a flag that is something like: --startup-scripts-dir <path>? or were you thinking something else?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we also use this here:

validator-lab/src/lib.rs

Lines 19 to 21 in 044c9d2

pub fn get_solana_root() -> PathBuf {
PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("$CARGO_MANIFEST_DIR")).to_path_buf()
}
. We need this for the release-channel deployment method. So maybe we just pass in a --validator-lab-dir <absolute-path-to-validator-lab-directory>. From here we can get startup script directory and then of course the directory needed for release-channel deployment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense to me!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool! it's implemented in: #8

let files_to_copy = ["bootstrap-startup-script.sh", "common.sh"];
for file_name in files_to_copy.iter() {
Self::copy_file_to_docker(&manifest_path, docker_path, file_name)?;
}
}
}

let (solana_build_directory, startup_script_directory) =
if let DeployMethod::ReleaseChannel(_) = self.deploy_method {
("solana-release", "./src/scripts".to_string())
} else {
("farf", format!("./docker-build/{validator_type}"))
};

let dockerfile = format!(
r#"
FROM {}
RUN apt-get update
RUN apt-get install -y iputils-ping curl vim bzip2

RUN useradd -ms /bin/bash solana
RUN adduser solana sudo
USER solana

RUN mkdir -p /home/solana/k8s-cluster-scripts
# TODO: this needs to be changed for non bootstrap, this should be ./src/scripts/<validator-type>-startup-scripts.sh
COPY {startup_script_directory}/bootstrap-startup-script.sh /home/solana/k8s-cluster-scripts

RUN mkdir -p /home/solana/ledger
COPY --chown=solana:solana ./config-k8s/bootstrap-validator /home/solana/ledger

RUN mkdir -p /home/solana/.cargo/bin

COPY ./{solana_build_directory}/bin/ /home/solana/.cargo/bin/
COPY ./{solana_build_directory}/version.yml /home/solana/

RUN mkdir -p /home/solana/config
ENV PATH="/home/solana/.cargo/bin:${{PATH}}"

WORKDIR /home/solana
Comment on lines +154 to +177
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could consider to use less RUN command to reduce the image size.


"#,
self.base_image
);

debug!("dockerfile: {dockerfile:?}");
std::fs::write(
docker_path.join("Dockerfile"),
content.unwrap_or(dockerfile.as_str()),
)?;
Ok(())
}
}
13 changes: 5 additions & 8 deletions src/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ impl Genesis {
.for_each(|account_type| {
args.push(
self.config_dir
.join(format!("bootstrap-validator/{}.json", account_type))
.join(format!("bootstrap-validator/{account_type}.json"))
.to_string_lossy()
.to_string(),
);
Expand All @@ -269,22 +269,19 @@ impl Genesis {
args
}

pub fn setup_spl_args(
&self,
solana_root_path: &PathBuf,
) -> Result<Vec<String>, Box<dyn Error>> {
pub fn setup_spl_args(&self, solana_root_path: &Path) -> Result<Vec<String>, Box<dyn Error>> {
let fetch_spl_file = solana_root_path.join("fetch-spl.sh");
fetch_spl(&fetch_spl_file)?;

// add in spl stuff
// add in spl
let spl_file = solana_root_path.join("spl-genesis-args.sh");
parse_spl_genesis_file(&spl_file)
}

pub fn generate(
&mut self,
solana_root_path: &PathBuf,
build_path: &PathBuf,
solana_root_path: &Path,
build_path: &Path,
) -> Result<(), Box<dyn Error>> {
let mut args = self.setup_genesis_flags();
let mut spl_args = self.setup_spl_args(solana_root_path)?;
Expand Down
6 changes: 4 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,14 @@ pub enum ValidatorType {
Client,
}

pub mod docker;
pub mod genesis;
pub mod kubernetes;
pub mod release;

static SUN: Emoji = Emoji("🌞 ", "");
static BUILD: Emoji = Emoji("👷 ", "");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👷‍♂️

static PACKAGE: Emoji = Emoji("📦 ", "");
static SUN: Emoji = Emoji("🌞 ", "");
static TRUCK: Emoji = Emoji("🚚 ", "");

/// Creates a new process bar for processing that will take an unknown amount of time
Expand All @@ -78,7 +80,7 @@ pub fn cat_file(path: &PathBuf) -> std::io::Result<()> {
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
info!("{:?}:\n{}", path.file_name(), contents);
info!("{:?}:\n{contents}", path.file_name());

Ok(())
}
Expand Down
Loading