Compare commits

..

13 Commits

Author SHA1 Message Date
0fc89e6e40 Added a cli and arg parsing
All checks were successful
Gitea Organization/dotfiles/pipeline/head This commit looks good
2023-02-14 17:23:22 +01:00
20f340e14f Merge branch 'jenkins' into installer
All checks were successful
Gitea Organization/dotfiles/pipeline/head This commit looks good
# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	Jenkinsfile
#	src/main.rs
#	src/update.rs
2023-02-13 20:11:14 +01:00
208a4e9e79 Use Jenkins for updates
Some checks failed
Gitea Organization/dotfiles/pipeline/head This commit looks good
Gitea Organization/dotfiles/pipeline/pr-installer There was a failure building this commit
2023-02-13 20:05:20 +01:00
00a38cbf03 Updated to jenkins and gitea (#4)
All checks were successful
Gitea Organization/dotfiles/pipeline/head This commit looks good
Co-authored-by: Mutzi <root@mattv.de>
Reviewed-on: #4
2023-02-12 22:36:01 +00:00
1c235d1949 Fixed warnings
All checks were successful
Gitea Organization/dotfiles/pipeline/pr-installer This commit looks good
2023-02-12 23:32:30 +01:00
60fce326dd Use commit for version again
All checks were successful
Gitea Organization/dotfiles/pipeline/pr-installer This commit looks good
2023-02-12 23:30:26 +01:00
cb8ef1e2e8 Switched update to gitea
All checks were successful
Gitea Organization/dotfiles/pipeline/head This commit looks good
Gitea Organization/dotfiles/pipeline/pr-installer This commit looks good
2023-02-12 23:15:09 +01:00
577d9519fa Removed gitlab CI 2023-02-12 22:39:29 +01:00
6fed41de0a Testing with Jenkins
All checks were successful
Gitea Organization/dotfiles/pipeline/head This commit looks good
2023-02-12 22:36:36 +01:00
d5abe0c38a Testing with Jenkins
All checks were successful
Gitea Organization/dotfiles/pipeline/head This commit looks good
2023-02-12 22:21:58 +01:00
5657c570df Testing with Jenkins
All checks were successful
Gitea Organization/dotfiles/pipeline/head This commit looks good
2023-02-12 22:13:29 +01:00
4f09a0eb81 Testing with Jenkins
All checks were successful
Gitea Organization/dotfiles/pipeline/head This commit looks good
2023-02-11 16:59:41 +01:00
8461871d7a Testing with Jenkins
All checks were successful
Gitea Organization/dotfiles/pipeline/head This commit looks good
2023-02-11 16:44:55 +01:00
16 changed files with 291 additions and 206 deletions

View File

@@ -1,19 +0,0 @@
stages:
- build
variables:
PACKAGE_REGISTRY_URL: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/installer"
build:
stage: build
image: rust:alpine
rules:
- if: $CI_COMMIT_BRANCH == "installer"
script:
- apk add pkgconf musl-dev curl
- RUSTFLAGS='-C target-feature=+crt-static -C link-self-contained=yes -C link-arg=-s' cargo build --release --target x86_64-unknown-linux-musl
- cp target/x86_64-unknown-linux-musl/release/dotfiles_installer ./installer-amd64
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file installer-amd64 "${PACKAGE_REGISTRY_URL}/dev-$CI_COMMIT_SHA/installer-amd64"'
artifacts:
paths:
- installer-amd64

1
.idea/vcs.xml generated
View File

@@ -2,5 +2,6 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$/repo" vcs="Git" />
</component>
</project>

49
Cargo.lock generated
View File

@@ -8,11 +8,10 @@ version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b85f766c20e6ae766956f7a2fcc4e0931e79a7e1f48b29132b5d647021114914"
dependencies = [
"base64",
"http",
"log",
"rustls",
"serde",
"serde_json",
"url",
"webpki",
"webpki-roots",
@@ -24,12 +23,24 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bpaf"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "863c0b21775e45ebf9bbb3a6d0cd9b3421c88a036e825359e3d4015561f3e23c"
[[package]]
name = "bumpalo"
version = "3.12.0"
@@ -84,6 +95,7 @@ name = "dotfiles_installer"
version = "1.0.0"
dependencies = [
"attohttpc",
"bpaf",
"dialoguer",
"indicatif",
"serde",
@@ -316,12 +328,6 @@ dependencies = [
"webpki",
]
[[package]]
name = "ryu"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
[[package]]
name = "sct"
version = "0.7.0"
@@ -352,22 +358,11 @@ dependencies = [
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_spanned"
version = "0.6.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c68e921cef53841b8925c2abadd27c9b891d9613bdc43d6b823062866df38e8"
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
dependencies = [
"serde",
]
@@ -426,9 +421,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "toml"
version = "0.6.0"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb9d890e4dc9298b70f740f615f2e05b9db37dce531f6b24fb77ac993f9f217"
checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6"
dependencies = [
"serde",
"serde_spanned",
@@ -438,18 +433,18 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.5.1"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5"
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.18.0"
version = "0.19.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "729bfd096e40da9c001f778f5cdecbd2957929a24e10e5883d9392220a751581"
checksum = "5e6a7712b49e1775fb9a7b998de6635b299237f48b404dde71704f2e0e7f37e5"
dependencies = [
"indexmap",
"nom8",

View File

@@ -6,9 +6,11 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
toml = "0.7.2"
bpaf = "0.7.8"
dialoguer = "0.10.3"
indicatif = "0.17.3"
toml = "0.6.0"
[dependencies.serde]
version = "1.0.152"
@@ -18,6 +20,6 @@ features = ["derive"]
version = "0.24.0"
default_features = false
features = [
"json",
"basic-auth",
"tls-rustls-webpki-roots"
]

4
Jenkinsfile vendored
View File

@@ -10,10 +10,10 @@ pipeline {
TOKEN = credentials('abd7020c-43d6-485b-ae09-2f9b484d9c15')
}
steps {
sh 'apk add pkgconf musl-dev curl'
sh 'apk add pkgconf musl-dev'
sh 'cargo build --release --target x86_64-unknown-linux-musl'
sh 'cp target/x86_64-unknown-linux-musl/release/dotfiles_installer ./installer-amd64'
sh 'curl --user root:${TOKEN} --upload-file ./installer-amd64 https://gitea.mattv.de/api/packages/root/generic/installer/${BUILD_NUMBER}/installer-amd64'
archiveArtifacts artifacts: 'installer-amd64', allowEmptyArchive: false, onlyIfSuccessful: true
}
}
}

68
src/cli/mod.rs Normal file
View File

@@ -0,0 +1,68 @@
use bpaf::{command, construct, Parser};
use crate::common::config::ModToml;
use crate::common::{operations, repository};
use crate::common::operations::NamedModule;
#[derive(Debug, Clone)]
pub enum CliCommand {
Install(Vec<String>),
Collect(Vec<String>),
Upload(String),
List(),
Pull(),
Diff()
}
pub fn get_parser() -> impl Parser<CliCommand> {
let modules = bpaf::positional::<String>("Modules").many();
let install =
command("install", construct!(CliCommand::Install(modules)).to_options())
.help("Install modules");
let modules = bpaf::positional::<String>("Modules").many();
let collect =
command("collect", construct!(CliCommand::Collect(modules)).to_options())
.help("Collect modules");
let commit_msg = bpaf::positional::<String>("Commit message");
let upload =
command("upload", construct!(CliCommand::Upload(commit_msg)).to_options())
.help("Upload files");
let list = command("list", construct!(CliCommand::List()).to_options())
.help("List modules");
let pull = command("pull", construct!(CliCommand::Pull()).to_options())
.help("Downloads update");
let diff = command("diff", construct!(CliCommand::Diff()).to_options())
.help("Show diff of current changes");
construct!([
install,
collect,
upload,
list,
pull,
diff
])
}
pub fn run(config: &ModToml, command: CliCommand) {
let map_mod = |name: String| -> NamedModule {
match config.modules.get(&name) {
None => { println!("Module {name} doesn't exist"); std::process::exit(1); }
Some(v) => NamedModule(name, v.clone())
}
};
match command {
CliCommand::Install(mods) => operations::install(&mods.into_iter().map(map_mod).collect::<Vec<NamedModule>>()),
CliCommand::Collect(mods) => operations::collect(&mods.into_iter().map(map_mod).collect::<Vec<NamedModule>>()),
CliCommand::Upload(msg) => operations::upload(|| msg),
CliCommand::List() => config.modules.iter().for_each(|m| println!("Module {}", m.0)),
CliCommand::Pull() => repository::pull_repo(),
CliCommand::Diff() => repository::diff()
}
}

5
src/common/mod.rs Normal file
View File

@@ -0,0 +1,5 @@
pub mod repository;
pub mod config;
pub mod operations;
pub mod utils;
pub mod update;

View File

@@ -1,8 +1,16 @@
use crate::config::{ModToml, Module};
use crate::prompt::multi_select;
use crate::common::{repository, utils, config::{ModToml, Module}};
#[derive(Debug)]
struct NamedModule(String, Module);
pub struct NamedModule(pub String, pub Module);
impl From<&ModToml> for Vec<NamedModule> {
fn from(config: &ModToml) -> Self {
config.modules.iter()
.map(|v| NamedModule(v.0.clone(), v.1.clone()))
.collect()
}
}
impl ToString for NamedModule {
fn to_string(&self) -> String {
self.0.clone()
@@ -28,12 +36,12 @@ fn install_mod(m: &NamedModule) {
let pb = indicatif::ProgressBar::new(0);
pb.set_style(indicatif::ProgressStyle::with_template("{spinner} [{wide_bar}] {pos:>6}/{len:6}").unwrap().progress_chars("#>-"));
if let Err(err) = crate::utils::remove_dir(dst, &m.1.ignore, &pb) {
if let Err(err) = utils::remove_dir(dst, &m.1.ignore, &pb) {
println!("Failed to delete directory:\n{}", err);
continue 'content_iter;
}
if let Err(err) = crate::utils::copy_dir(&src, dst, &[], &pb) {
if let Err(err) = utils::copy_dir(&src, dst, &[], &pb) {
println!("Failed to copy directory:\n{}", err);
}
}
@@ -53,7 +61,7 @@ fn collect_mod(m: &NamedModule) {
let pb = indicatif::ProgressBar::new(0);
pb.set_style(indicatif::ProgressStyle::with_template("{spinner} [{wide_bar}] {pos:>6}/{len:6}").unwrap().progress_chars("#>-"));
if let Err(err) = crate::utils::remove_dir(&dst, &[], &pb) {
if let Err(err) = utils::remove_dir(&dst, &[], &pb) {
println!("Failed to delete directory:\n{}", err);
continue 'content_iter;
}
@@ -63,7 +71,7 @@ fn collect_mod(m: &NamedModule) {
continue 'content_iter;
}
if let Err(err) = crate::utils::copy_dir(src, &dst, &m.1.ignore, &pb) {
if let Err(err) = utils::copy_dir(src, &dst, &m.1.ignore, &pb) {
println!("Failed to copy source content:\n{}", err);
continue 'content_iter;
}
@@ -71,14 +79,9 @@ fn collect_mod(m: &NamedModule) {
}
}
pub fn install(config: &ModToml) {
let to_install = multi_select(
Some("Which modules do you want to install?"),
config.modules.iter().map(|v| NamedModule(v.0.clone(), v.1.clone())).collect()
);
for m in to_install {
install_mod(&m);
pub fn install(mods: &[NamedModule]) {
for m in mods {
install_mod(m);
if let Some(on_install) = &m.1.on_install {
match std::process::Command::new("sh").arg("-c").arg(on_install).status() {
@@ -89,26 +92,21 @@ pub fn install(config: &ModToml) {
}
}
pub fn collect(config: &ModToml) {
let to_collect = multi_select(
Some("Which modules do you want to collect?"),
config.modules.iter().map(|v| NamedModule(v.0.clone(), v.1.clone())).collect()
);
for m in to_collect {
collect_mod(&m);
pub fn collect(mods: &[NamedModule]) {
for m in mods {
collect_mod(m);
}
}
pub fn upload() {
if !crate::repository::is_clean() {
let msg = crate::prompt::input("Commit message");
if !crate::repository::commit_changes(&msg) {
pub fn upload<F: FnOnce() -> String>(get_commit_msg: F) {
if !repository::is_clean() {
let msg = get_commit_msg();
if !repository::commit_changes(&msg) {
println!("Failed to commit");
return;
}
}
if !crate::repository::push_repo() {
if !repository::push_repo() {
println!("Failed to push to origin");
}
}

View File

@@ -48,7 +48,7 @@ fn check_config() -> bool {
fn clone_repo() -> bool {
println!("Cloning repo...");
if !Path::new("repo").exists() { std::fs::create_dir("repo").unwrap(); }
git!("clone", "git@ssh.gitlab.mattv.de:root/dotfiles.git", ".").success()
git!("clone", "gitea@gitea.mattv.de:root/dotfiles.git", ".").success()
&& check_config()
}

36
src/common/update.rs Normal file
View File

@@ -0,0 +1,36 @@
use std::env::current_exe;
use std::fs;
use std::fs::File;
use std::os::unix::process::CommandExt;
use std::path::Path;
const JENKINS_KEY: &str = "1196373359a3f17bbb8f0f5685b8152276";
pub fn check_for_updates(version: u64, name: &str) {
print!("Checking for updates... ");
let newest: u64 = attohttpc::get(format!("https://jenkins.mattv.de/job/Gitea%20Organization/job/dotfiles/job/{name}/lastSuccessfulBuild/buildNumber"))
.basic_auth("root", Some(JENKINS_KEY))
.send().unwrap()
.text().unwrap()
.parse().unwrap();
if newest > version {
println!("New version {name}-{newest}");
let exe = current_exe().unwrap();
let temp = Path::new("temp");
print!("Downloading... ");
attohttpc::get(format!("https://jenkins.mattv.de/job/Gitea%20Organization/job/dotfiles/job/{name}/{newest}/artifact/installer-amd64"))
.basic_auth("root", Some(JENKINS_KEY))
.send().unwrap()
.write_to(File::create(temp).unwrap()).unwrap();
println!("Done");
fs::set_permissions(&temp, exe.metadata().unwrap().permissions()).unwrap();
fs::rename(&temp, &exe).unwrap();
std::process::Command::new(exe).args(std::env::args()).exec();
std::process::exit(1);
}
println!("No new version");
}

View File

@@ -1,37 +1,36 @@
mod prompt;
mod repository;
mod config;
mod operations;
mod utils;
mod update;
mod common;
mod tui;
mod cli;
use bpaf::{construct, Parser};
use crate::common::{config, repository, update};
#[derive(Debug, Clone)]
enum MainMenu {
Install,
Collect,
Pull,
Diff,
Upload,
Quit
}
impl ToString for MainMenu {
fn to_string(&self) -> String {
match self {
MainMenu::Install => "Install files",
MainMenu::Collect => "Collect files",
MainMenu::Pull => "Pull from git",
MainMenu::Diff => "View git diff",
MainMenu::Upload => "(Commit) and push to git",
MainMenu::Quit => "Exit"
}.to_string()
}
enum Commands {
Tui(),
Cli(cli::CliCommand)
}
fn main() {
if let Some(version) = option_env!("CI_COMMIT_SHA") {
println!("Starting installer version {}", version);
update::check_for_updates(version);
let tui_cmd =
bpaf::command("tui", construct!(Commands::Tui()).to_options())
.help("Terminal ui for interactive use");
let cli_parser = cli::get_parser();
let cli_cmd =
bpaf::command("cli", construct!(Commands::Cli(cli_parser)).to_options())
.help("Command line for script usage");
let opt = construct!([tui_cmd, cli_cmd])
.fallback(Commands::Tui())
.to_options()
.descr("Dotfiles multitool")
.run();
if let (Some(version), Some(name)) = (option_env!("BUILD_ID"), option_env!("JOB_BASE_NAME")) {
println!("Starting installer version {name}-{version}");
update::check_for_updates(version.parse().unwrap(), name);
} else {
println!("Starting installer version unknown");
}
@@ -55,25 +54,8 @@ fn main() {
}
};
'main_loop: loop {
let res = prompt::select(
Some("What do you want to do?"),
vec![
MainMenu::Install,
MainMenu::Collect,
MainMenu::Pull,
MainMenu::Diff,
MainMenu::Upload,
MainMenu::Quit
]
);
match res {
MainMenu::Install => operations::install(&mods),
MainMenu::Collect => operations::collect(&mods),
MainMenu::Pull => repository::pull_repo(),
MainMenu::Diff => repository::diff(),
MainMenu::Upload => operations::upload(),
MainMenu::Quit => break 'main_loop
}
match opt {
Commands::Tui() => tui::run(&mods),
Commands::Cli(command) => cli::run(&mods, command)
}
}

View File

@@ -1,38 +0,0 @@
use dialoguer::{Input, MultiSelect, Select};
pub fn select<F>(title: Option<&str>, mut options: Vec<F>) -> F
where F: ToString
{
let mut sel = Select::new();
sel.items(&options);
sel.default(0);
if let Some(title) = title {
sel.with_prompt(title);
}
let sel = sel.interact().expect("User didn't enter anything");
options.remove(sel)
}
pub fn multi_select<F>(title: Option<&str>, options: Vec<F>) -> Vec<F>
where F: ToString
{
let mut sel = MultiSelect::new();
sel.items(&options);
if let Some(title) = title {
sel.with_prompt(title);
}
let sel = sel.interact().expect("User didn't enter anything");
options.into_iter()
.enumerate()
.filter_map(|(i, v)| sel.contains(&i).then_some(v))
.collect()
}
pub fn input(title: &str) -> String {
Input::new()
.with_prompt(title)
.interact_text()
.expect("User didn't enter anything")
}

95
src/tui/mod.rs Normal file
View File

@@ -0,0 +1,95 @@
use dialoguer::{Input, MultiSelect, Select};
use crate::common::{operations, repository};
use crate::common::config::ModToml;
#[derive(Debug, Clone)]
enum MainMenu {
Install,
Collect,
Pull,
Diff,
Upload,
Quit
}
impl ToString for MainMenu {
fn to_string(&self) -> String {
match self {
MainMenu::Install => "Install files",
MainMenu::Collect => "Collect files",
MainMenu::Pull => "Pull from git",
MainMenu::Diff => "View git diff",
MainMenu::Upload => "(Commit) and push to git",
MainMenu::Quit => "Exit"
}.to_string()
}
}
pub fn select<F>(title: Option<&str>, mut options: Vec<F>) -> F
where F: ToString
{
let mut sel = Select::new();
sel.items(&options);
sel.default(0);
if let Some(title) = title {
sel.with_prompt(title);
}
let sel = sel.interact().expect("User didn't enter anything");
options.remove(sel)
}
pub fn multi_select<F>(title: Option<&str>, options: Vec<F>) -> Vec<F>
where F: ToString
{
let mut sel = MultiSelect::new();
sel.items(&options);
if let Some(title) = title {
sel.with_prompt(title);
}
let sel = sel.interact().expect("User didn't enter anything");
options.into_iter()
.enumerate()
.filter_map(|(i, v)| sel.contains(&i).then_some(v))
.collect()
}
pub fn input(title: &str) -> String {
Input::new()
.with_prompt(title)
.interact_text()
.expect("User didn't enter anything")
}
pub fn run(config: &ModToml) {
'main_loop: loop {
let res = select(
Some("What do you want to do?"),
vec![
MainMenu::Install,
MainMenu::Collect,
MainMenu::Pull,
MainMenu::Diff,
MainMenu::Upload,
MainMenu::Quit
]
);
match res {
MainMenu::Install =>
operations::install(&multi_select(
Some("Which modules do you want to install?"),
config.into()
)),
MainMenu::Collect =>
operations::collect(&multi_select(
Some("Which modules do you want to collect?"),
config.into()
)),
MainMenu::Pull => repository::pull_repo(),
MainMenu::Diff => repository::diff(),
MainMenu::Upload => operations::upload(|| input("Commit message")),
MainMenu::Quit => break 'main_loop
}
}
}

View File

@@ -1,40 +0,0 @@
use std::env::current_exe;
use std::fs;
use std::fs::File;
use std::os::unix::process::CommandExt;
use std::path::Path;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct Pipeline {
sha: String
}
#[derive(Debug, Deserialize)]
struct PackageEntry {
version: String,
pipeline: Pipeline
}
pub fn check_for_updates(version: &str) {
print!("Checking for updates... ");
let resp: Vec<PackageEntry> = attohttpc::get("https://gitlab.mattv.de/api/v4/projects/43/packages?name=installer&sort=desc").send().unwrap().json().unwrap();
let newest = resp.first().unwrap();
if newest.pipeline.sha != version {
println!("New version exists");
let exe = current_exe().unwrap();
let temp = Path::new("temp");
print!("Downloading... ");
attohttpc::get(format!("https://gitlab.mattv.de/api/v4/projects/43/packages/generic/installer/{}/installer-amd64", newest.version)).send().unwrap().write_to(File::create(temp).unwrap()).unwrap();
println!("Done");
fs::set_permissions(&temp, exe.metadata().unwrap().permissions()).unwrap();
fs::rename(&temp, &exe).unwrap();
std::process::Command::new(exe).exec();
std::process::exit(1);
}
println!("No new version");
}