pub struct OtaClient {
client: Client,
token: Option<SecretString>,
}Expand description
HTTP client for downloading OTA updates from GitHub.
This client handles the complete OTA update workflow:
- Fetching PR information from GitHub API
- Finding associated workflow runs
- Downloading build artifacts and stable releases
- Extracting and deploying updates
§Security
The GitHub personal access token is optional and wrapped in SecretString
from the secrecy crate to prevent accidental exposure in logs, debug output,
or error messages. The token is automatically wiped from memory when dropped.
Access to the token value requires explicit use of .expose_secret().
Token is required for:
- PR build downloads
- Default branch artifact downloads
Token is optional for:
- Stable release downloads (public URLs)
Fields§
§client: Client§token: Option<SecretString>Implementations§
Source§impl OtaClient
impl OtaClient
Sourcepub fn new(github_token: Option<SecretString>) -> Result<Self, OtaError>
pub fn new(github_token: Option<SecretString>) -> Result<Self, OtaError>
Creates a new OTA client with optional GitHub authentication.
Initializes an HTTP client with TLS configured using webpki-roots certificates for secure communication with GitHub’s API.
§Arguments
github_token- Optional personal access token wrapped inSecretStringfor secure handling. Required for artifact downloads, optional for stable release downloads.
§Errors
Returns OtaError::TlsConfig if the HTTP client fails to initialize
with the provided TLS configuration.
§Security
The token is stored securely and will never appear in debug output or logs. It is only exposed when making authenticated API requests.
Sourcefn get_token(&self) -> Result<&SecretString, OtaError>
fn get_token(&self) -> Result<&SecretString, OtaError>
Returns a reference to the GitHub token if available.
§Errors
Returns OtaError::NoToken if no token is configured.
Sourcepub fn download_pr_artifact<F>(
&self,
pr_number: u32,
progress_callback: F,
) -> Result<PathBuf, OtaError>where
F: FnMut(OtaProgress),
pub fn download_pr_artifact<F>(
&self,
pr_number: u32,
progress_callback: F,
) -> Result<PathBuf, OtaError>where
F: FnMut(OtaProgress),
Downloads the build artifact from a GitHub pull request.
This performs the complete download workflow:
- Verifies sufficient disk space (100MB required)
- Fetches PR metadata to get the commit SHA
- Finds the associated “Cargo” workflow run
- Locates artifacts matching “cadmus-kobo-pr*” pattern
- Downloads the artifact ZIP file to
/tmp/cadmus-ota-{pr_number}.zip
GitHub authentication is required for this operation.
§Arguments
pr_number- The pull request number from ogkevin/cadmus repositoryprogress_callback- Function called with progress updates during download
§Returns
The path to the downloaded ZIP file on success.
§Errors
OtaError::InsufficientSpace- Less than 100MB available in /tmpOtaError::NoToken- GitHub token not configuredOtaError::PrNotFound- PR number doesn’t exist in repositoryOtaError::NoArtifacts- No matching build artifacts found for the PROtaError::Api- GitHub API request failedOtaError::Request- Network communication failedOtaError::Io- Failed to write downloaded file to disk
Sourcepub fn download_default_branch_artifact<F>(
&self,
progress_callback: F,
) -> Result<PathBuf, OtaError>where
F: FnMut(OtaProgress),
pub fn download_default_branch_artifact<F>(
&self,
progress_callback: F,
) -> Result<PathBuf, OtaError>where
F: FnMut(OtaProgress),
Downloads the latest build artifact from the default branch.
This performs the complete download workflow for default branch builds:
- Verifies sufficient disk space (100MB required)
- Queries GitHub API for the latest successful
cargo.ymlworkflow run on the default branch - Locates artifacts matching “cadmus-kobo-{sha}” pattern (or “cadmus-kobo-test-{sha}” with
testfeature) - Downloads the artifact ZIP file to
/tmp/cadmus-ota-{sha}.zip
GitHub authentication is required for this operation.
§Arguments
progress_callback- Function called with progress updates during download
§Returns
The path to the downloaded ZIP file on success.
§Errors
OtaError::InsufficientSpace- Less than 100MB available in /tmpOtaError::NoToken- GitHub token not configuredOtaError::NoDefaultBranchArtifacts- No matching build artifacts foundOtaError::Api- GitHub API request failedOtaError::Request- Network communication failedOtaError::Io- Failed to write downloaded file to disk
Sourcepub fn download_stable_release_artifact<F>(
&self,
progress_callback: F,
) -> Result<PathBuf, OtaError>where
F: FnMut(OtaProgress),
pub fn download_stable_release_artifact<F>(
&self,
progress_callback: F,
) -> Result<PathBuf, OtaError>where
F: FnMut(OtaProgress),
Downloads the latest stable release artifact from GitHub releases.
This performs the complete download workflow for stable releases:
- Verifies sufficient disk space (100MB required)
- Fetches the latest release from GitHub API
- Locates the
KoboRoot.tgzasset in the release - Downloads the file to
/tmp/cadmus-ota-stable-release.tgz
GitHub authentication is not required for this operation as release assets are downloaded from public URLs without Authorization headers.
§Arguments
progress_callback- Function called with progress updates during download
§Returns
The path to the downloaded KoboRoot.tgz file on success.
§Errors
OtaError::InsufficientSpace- Less than 100MB available in /tmpOtaError::Api- GitHub API request failedOtaError::Request- Network communication failedOtaError::NoArtifacts- KoboRoot.tgz not found in latest releaseOtaError::Io- Failed to write downloaded file to disk
Sourcepub fn deploy(&self, kobo_root_path: PathBuf) -> Result<PathBuf, OtaError>
pub fn deploy(&self, kobo_root_path: PathBuf) -> Result<PathBuf, OtaError>
Deploys KoboRoot.tgz from the specified path directly without extraction.
Used when the artifact is already in the correct format (e.g., stable releases that are distributed as bare KoboRoot.tgz files).
§Arguments
kobo_root_path- Path to the KoboRoot.tgz file to deploy
§Returns
The path where the file was deployed, or an error if deployment fails.
§Errors
OtaError::Io- Failed to read or write files
Sourcefn deploy_bytes(&self, data: &[u8]) -> Result<PathBuf, OtaError>
fn deploy_bytes(&self, data: &[u8]) -> Result<PathBuf, OtaError>
Deploys KoboRoot.tgz data to the appropriate location.
Writes the provided data to the deployment path determined by the build configuration:
- Test builds: temp directory
- Emulator builds: /tmp/.kobo/KoboRoot.tgz
- Production builds: {INTERNAL_CARD_ROOT}/.kobo/KoboRoot.tgz
§Arguments
data- The KoboRoot.tgz file contents to deploy
§Returns
The deployment path where KoboRoot.tgz was written.
§Errors
OtaError::Io- Failed to create directories or write deployment file
Sourcepub fn extract_and_deploy(&self, zip_path: PathBuf) -> Result<PathBuf, OtaError>
pub fn extract_and_deploy(&self, zip_path: PathBuf) -> Result<PathBuf, OtaError>
Extracts KoboRoot.tgz from the artifact and deploys it for installation.
Opens the downloaded ZIP archive, locates the KoboRoot.tgz file,
extracts it, and writes it to /mnt/onboard/.kobo/KoboRoot.tgz
where the Kobo device will automatically install it on next reboot.
§Arguments
zip_path- Path to the downloaded artifact ZIP file
§Returns
The deployment path where KoboRoot.tgz was written.
§Errors
OtaError::ZipError- Failed to open or read ZIP archiveOtaError::DeploymentError- KoboRoot.tgz not found in archiveOtaError::Io- Failed to write deployment file
Sourcefn fetch_default_branch(&self) -> Result<String, OtaError>
fn fetch_default_branch(&self) -> Result<String, OtaError>
Queries the GitHub API for the repository’s default branch name.
Sourcefn find_artifact_in_run(
&self,
run_id: u64,
name_prefix: &str,
) -> Result<Artifact, OtaError>
fn find_artifact_in_run( &self, run_id: u64, name_prefix: &str, ) -> Result<Artifact, OtaError>
Fetches artifacts for a workflow run and finds one matching the given prefix.
Sourcefn download_by_url_to_path<F>(
&self,
url: &str,
total_size: u64,
download_path: &PathBuf,
progress_callback: &mut F,
use_auth: bool,
) -> Result<(), OtaError>where
F: FnMut(OtaProgress),
fn download_by_url_to_path<F>(
&self,
url: &str,
total_size: u64,
download_path: &PathBuf,
progress_callback: &mut F,
use_auth: bool,
) -> Result<(), OtaError>where
F: FnMut(OtaProgress),
Downloads a file from a URL with chunked transfer and progress reporting.
Uses HTTP Range headers to request the file in chunks for resilience against network interruptions.
§Arguments
url- The complete download URLtotal_size- Total file size in bytesdownload_path- Path where the file should be savedprogress_callback- Function called with progress updatesuse_auth- Whether to include Authorization header in requests
§Returns
Success if the file is written to disk, error otherwise.
Sourcefn download_artifact_to_path<F>(
&self,
artifact: &Artifact,
download_path: &PathBuf,
progress_callback: &mut F,
) -> Result<(), OtaError>where
F: FnMut(OtaProgress),
fn download_artifact_to_path<F>(
&self,
artifact: &Artifact,
download_path: &PathBuf,
progress_callback: &mut F,
) -> Result<(), OtaError>where
F: FnMut(OtaProgress),
Downloads an artifact ZIP to the specified path with chunked transfer and progress reporting.
GitHub authentication is required for this operation.
Sourcefn download_chunk_with_retries(
&self,
url: &str,
start: u64,
end: u64,
use_auth: bool,
) -> Result<Vec<u8>, OtaError>
fn download_chunk_with_retries( &self, url: &str, start: u64, end: u64, use_auth: bool, ) -> Result<Vec<u8>, OtaError>
Downloads a specific byte range of a file with automatic retry logic.
Uses HTTP Range headers to request a specific chunk of the artifact. Implements exponential backoff retry strategy for failed downloads.
§Arguments
url- The download URLstart- Starting byte offset (inclusive)end- Ending byte offset (inclusive)
§Returns
The downloaded chunk data as a byte vector.
§Errors
Returns an error if all retry attempts fail.
Sourcefn download_chunk(
&self,
url: &str,
start: u64,
end: u64,
use_auth: bool,
) -> Result<Vec<u8>, OtaError>
fn download_chunk( &self, url: &str, start: u64, end: u64, use_auth: bool, ) -> Result<Vec<u8>, OtaError>
Downloads a specific byte range from a URL using HTTP Range header.
§Arguments
url- The download URLstart- Starting byte offset (inclusive)end- Ending byte offset (inclusive)use_auth- Whether to include Authorization header
§Returns
The downloaded chunk data as a byte vector.
§Errors
Returns an error if the download fails or times out.
Sourcefn download_release_asset<F>(
&self,
asset: &ReleaseAsset,
download_path: &PathBuf,
progress_callback: &mut F,
) -> Result<(), OtaError>where
F: FnMut(OtaProgress),
fn download_release_asset<F>(
&self,
asset: &ReleaseAsset,
download_path: &PathBuf,
progress_callback: &mut F,
) -> Result<(), OtaError>where
F: FnMut(OtaProgress),
Downloads a release asset to the specified path with chunked transfer and progress reporting.
GitHub authentication is not required for this operation as release assets are downloaded from public URLs.
Auto Trait Implementations§
impl Freeze for OtaClient
impl !RefUnwindSafe for OtaClient
impl Send for OtaClient
impl Sync for OtaClient
impl Unpin for OtaClient
impl !UnwindSafe for OtaClient
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be
downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further
downcast into Rc<ConcreteType> where ConcreteType implements Trait.§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.