Skip to content
This repository was archived by the owner on Feb 23, 2026. It is now read-only.
Closed
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ rand = "0.8.5"
reqwest = { version = "0.11.23", features = ["blocking", "brotli", "deflate", "gzip", "rustls-tls", "json"] }
rustls = { version = "0.21.10", default-features = false, features = ["quic"] }
solana-core = "1.18.8"
solana-ledger = "1.18.8"
solana-logger = "1.18.8"
solana-sdk = "1.18.8"
strum = "0.26.2"
Expand Down
12 changes: 6 additions & 6 deletions PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,21 @@
- [x] Create Genesis
- [x] Generate faucet and bootstrap accounts
- [x] Build genesis
- [ ] Docker Build
- [ ] Build Bootstrap Image
- [ ] Push Image to registry
- [x] Docker Build
- [x] Build Bootstrap Image
- [x] Push Image to registry
- [ ] Create & Deploy Secrets
- [ ] Bootstrap
- [x] Bootstrap
- [ ] Validator (regular)
- [ ] RPC nodes
- [ ] Client
- [ ] Create & Deploy Selector
- [ ] Bootstrap
- [x] Bootstrap
- [ ] Validator (regular)
- [ ] RPC nodes
- [ ] Client
- [ ] Create & Deploy Replica Set
- [ ] Bootstrap
- [x] Bootstrap
- [ ] Validator (regular)
- [ ] RPC nodes
- [ ] Client
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
```
240 changes: 240 additions & 0 deletions src/docker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
use {
crate::{new_spinner_progress_bar, release::DeployMethod, ValidatorType, BUILD, ROCKET},
log::*,
std::{
env,
error::Error,
fmt::{self, Display, Formatter},
fs,
path::{Path, PathBuf},
process::{Command, Stdio},
},
};

pub struct DockerImage {
registry: String,
validator_type: ValidatorType,
image_name: String,
tag: String,
}

impl DockerImage {
// Constructor to create a new instance of DockerImage
pub fn new(
registry: String,
validator_type: ValidatorType,
image_name: String,
tag: String,
) -> Self {
DockerImage {
registry,
validator_type,
image_name,
tag,
}
}

pub fn validator_type(&self) -> ValidatorType {
self.validator_type
}
}

// Put DockerImage in format for building, pushing, and pulling
impl Display for DockerImage {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"{}/{}-{}:{}",
self.registry, self.validator_type, self.image_name, self.tag
)
}
}

pub struct DockerConfig {
pub base_image: String,
deploy_method: DeployMethod,
}

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

pub fn build_image(
&self,
solana_root_path: &Path,
docker_image: &DockerImage,
) -> Result<(), Box<dyn Error>> {
let validator_type = docker_image.validator_type();
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 docker_path = solana_root_path.join(format!("docker-build/{validator_type}"));
self.create_base_image(
solana_root_path,
docker_image,
&docker_path,
&validator_type,
)?;

Ok(())
}

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

// 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 {docker_image} -f {dockerfile:?} {context_path}");

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>),
}?;

if !output.status.success() {
return Err(output.status.to_string().into());
}
progress_bar.finish_and_clear();
info!("{validator_type} image build complete");

Ok(())
}

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"));
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} /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

"#,
self.base_image
);

debug!("dockerfile: {dockerfile:?}");
std::fs::write(
docker_path.join("Dockerfile"),
content.unwrap_or(dockerfile.as_str()),
)?;
Ok(())
}

pub fn push_image(docker_image: &DockerImage) -> Result<(), Box<dyn Error>> {
let progress_bar = new_spinner_progress_bar();
progress_bar.set_message(format!(
"{ROCKET}Pushing {} image to registry...",
docker_image.validator_type()
));
let command = format!("docker push '{}'", docker_image);
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>),
}?;

if !output.status.success() {
return Err(output.status.to_string().into());
}
progress_bar.finish_and_clear();
Ok(())
}
}
16 changes: 6 additions & 10 deletions src/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,7 @@ pub struct Genesis {
}

impl Genesis {
pub fn new(solana_root: &Path, flags: GenesisFlags) -> Self {
let config_dir = solana_root.join("config-k8s");
pub fn new(config_dir: PathBuf, flags: GenesisFlags) -> Self {
if config_dir.exists() {
std::fs::remove_dir_all(&config_dir).unwrap();
}
Expand Down Expand Up @@ -250,7 +249,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 +268,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
Loading