Skip to content

Commit

Permalink
feat(update): update your binary with runtasktic update
Browse files Browse the repository at this point in the history
  • Loading branch information
Joxit committed Oct 14, 2023
1 parent 0918877 commit 2465785
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 0 deletions.
15 changes: 15 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ jobs:
run: cargo install kokai
- name: Create Release Note
run: kokai release --ref ${{ github.ref }} --tag-from-ref . > RELEASE_NOTE.md
- name: Create sha256
run: |
cd target/release
cp runtasktic runtasktic-linux-x86_64
sha256sum -b runtasktic-linux-x86_64 > runtasktic-linux-x86_64.sha256
- name: Create Release
id: create_release
uses: actions/create-release@v1
Expand All @@ -47,3 +52,13 @@ jobs:
asset_path: ./target/release/runtasktic
asset_content_type: application/octet-stream
asset_name: runtasktic-linux-x86_64
- name: Upload SHA256 Asset
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./target/release/runtasktic-linux-x86_64.sha256
asset_content_type: application/octet-stream
asset_name: runtasktic-linux-x86_64.sha256
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ regex = "^1.3"
chrono = "^0.4"
cron = "0.12"
clap_complete = "^4.4"
sha256 = "1.4"
6 changes: 6 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ use crate::commands::completion::Completion;
use crate::commands::dot::Dot;
use crate::commands::exec::Exec;
use crate::commands::run::Run;
use crate::commands::update::Update;
use clap::Parser;

mod completion;
mod dot;
mod exec;
mod run;
mod update;

#[derive(Parser, Debug)]
pub enum Command {
Expand All @@ -27,6 +29,9 @@ pub enum Command {
/// Generate completion script for your shell.
#[command(name = "completion", subcommand)]
Completion(Completion),
/// Self update of the binary.
#[command(name = "update")]
Update(Update),
}

impl Command {
Expand All @@ -36,6 +41,7 @@ impl Command {
Command::Exec(executable) => executable.exec(),
Command::Dot(executable) => executable.exec(),
Command::Completion(executable) => executable.exec(),
Command::Update(executable) => executable.exec(),
}
}
}
162 changes: 162 additions & 0 deletions src/commands/update.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
use clap::Parser;
use std::io::Write;
use std::os::unix::fs::PermissionsExt;
use std::os::unix::prelude::OpenOptionsExt;
use std::{env, fs};

#[derive(Parser, Debug)]
pub struct Update {}

impl Update {
pub fn exec(&self) {
if let Err(err) = self.update() {
eprintln!("{}", err);
}
}

fn update(&self) -> Result<(), String> {
let path = env::current_exe().map_err(|msg| format!("Cannot find the executable: {}", msg))?;
let metadata = fs::metadata(&path)
.map_err(|msg| format!("Cannot find metadata of the executable: {}", msg))?;
if metadata.is_dir()
|| metadata.is_symlink()
|| !metadata.is_file()
|| metadata.permissions().readonly()
{
return Err(format!("The executable cannot be replaced."));
}
print!(
"The original executable has been located {}",
path.display()
);
let new_path = path.with_extension("tmp");
let mode = metadata.permissions().mode();
let latest_version = Self::get_latest_version()?;
let binary = Self::get_binary(&latest_version)?;
let digest = Self::get_sha256(&latest_version)?;
let binary_digest = sha256::digest(&binary);

if binary_digest != digest {
return Err(format!(
"Binary corrupted the downloaded sha256 does not match trusted: {} downloaded: {}",
digest, binary_digest
));
}

let mut file = fs::OpenOptions::new()
.create(true)
.write(true)
.mode(mode)
.open(&new_path)
.map_err(|msg| format!("Cannot create binary on disk: {}", msg))?;

file
.write_all(&binary)
.map_err(|msg| format!("Cannot write binary on disk: {}", msg))?;

fs::rename(&new_path, &path).map_err(|msg| {
format!(
"Cannot rename {} to {}: {}",
new_path.display(),
path.display(),
msg
)
})?;

Ok(())
}

fn get_latest_version() -> Result<String, String> {
let response = attohttpc::get("https://api.github.com/repos/Joxit/runtasktic/releases/latest")
.send()
.map_err(|msg| format!("Cannot get the latest version of the project: {}", msg))?;

if !response.is_success() {
return Err(format!(
"Cannot get the latest version of the project: {}",
response.status()
));
}

let response_json = json::parse(
&response
.text()
.map_err(|msg| format!("Cannot get the GitHub API release: {}", msg))?,
)
.map_err(|msg| format!("Cannot parse GitHub API release: {}", msg))?;

let obj = match &response_json {
json::JsonValue::Object(obj) => obj,
_ => {
return Err(format!(
"Cannot get the latest version of the project: {}",
response_json
))
}
};

let tag_name = obj.get("tag_name");

if let Some(body) = obj.get("body") {
println!("{}", body);
}

let err = format!("The tag cannot be parsed");
if let Some(test) = tag_name {
test.as_str().map(|version| version.to_string()).ok_or(err)
} else {
Err(err)
}
}

fn get_binary(version: &String) -> Result<Vec<u8>, String> {
let url = format!(
"https://github.com/Joxit/runtasktic/releases/download/{}/runtasktic-linux-x86_64",
version
);
let response = attohttpc::get(url)
.send()
.map_err(|msg| format!("Cannot get the binary of the project: {}", msg))?;

if !response.is_success() {
return Err(format!(
"Cannot get the binary of the project: {}",
response.status()
));
}

let bytes = response
.bytes()
.map_err(|msg| format!("Cannot collect all the bytes of the binary: {}", msg))?;

Ok(bytes)
}

fn get_sha256(version: &String) -> Result<String, String> {
let url = format!(
"https://github.com/Joxit/runtasktic/releases/download/{}/runtasktic-linux-x86_64.sha256",
version
);
let response = attohttpc::get(url)
.send()
.map_err(|msg| format!("Cannot get the binary's sha256 of the project: {}", msg))?;

if !response.is_success() {
return Err(format!(
"Cannot get the binary's sha256 of the project: {}",
response.status()
));
}

let sha256 = response
.text()
.map_err(|msg| format!("Cannot collect the binary's sha256 of the project: {}", msg))?
.trim()
.split_once(" ")
.unwrap_or_default()
.0
.to_string();

Ok(sha256)
}
}

0 comments on commit 2465785

Please sign in to comment.