topic/rust #2

Merged
ian merged 6 commits from topic/rust into main 2023-11-20 13:10:23 +00:00
10 changed files with 481 additions and 1650 deletions

View File

@ -1,42 +0,0 @@
name: Keep Running
run-name: ${{ gitea.actor }} is running spotify.local CI pipeline 🚀
on: [push]
concurrency: 'true'
jobs:
Build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
run: npm ci
- name: Building project
run: npm run build
- name: Creating Linux Archive
uses: vimtor/action-zip@v1.1
with:
files: out/kr-linux
dest: linux.zip
- name: Upload Linux Archive
uses: actions/upload-artifact@v3
with:
path: linux.zip
- name: Creating MacOS Archive
uses: vimtor/action-zip@v1.1
with:
files: out/kr-macos
dest: macos.zip
- name: Upload MacOS Archive
uses: actions/upload-artifact@v3
with:
path: macos.zip
- name: Creating Windows Archive
uses: vimtor/action-zip@v1.1
with:
files: out/kr-win.exe
dest: windows.zip
- name: Upload Windows Archive
uses: actions/upload-artifact@v3
with:
path: windows.zip

28
.github/workflows/release.yaml vendored Normal file
View File

@ -0,0 +1,28 @@
name: Releasing
run-name: ${{ gitea.actor }} is building a release releasing 🚀
on:
release:
types: [created]
jobs:
release:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-pc-windows-gnu
archive: zip
- target: x86_64-unknown-linux-musl
archive: tar.gz tar.xz tar.zst
- target: x86_64-apple-darwin
archive: zip
steps:
- uses: actions/checkout@master
- name: Compile and release
uses: rust-build/rust-build.action@v1.4.4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
RUSTTARGET: ${{ matrix.target }}
ARCHIVE_TYPES: ${{ matrix.archive }}

8
.gitignore vendored
View File

@ -1,6 +1,2 @@
.idea/
node_modules/
out/
# Random test file...
0
/target
/.idea

298
Cargo.lock generated Normal file
View File

@ -0,0 +1,298 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "anstream"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
[[package]]
name = "anstyle-parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
dependencies = [
"anstyle",
"windows-sys",
]
[[package]]
name = "bitflags"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
[[package]]
name = "clap"
version = "4.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
"terminal_size",
"unicase",
"unicode-width",
]
[[package]]
name = "clap_derive"
version = "4.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "errno"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "kr"
version = "0.1.0"
dependencies = [
"clap",
]
[[package]]
name = "libc"
version = "0.2.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
[[package]]
name = "linux-raw-sys"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829"
[[package]]
name = "proc-macro2"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustix"
version = "0.38.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "2.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "terminal_size"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
dependencies = [
"rustix",
"windows-sys",
]
[[package]]
name = "unicase"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
dependencies = [
"version_check",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-width"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"

16
Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[package]
name = "kr"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "4.4.8", features = ["derive", "unicode", "wrap_help"] }
[profile.release]
strip = true # Automatically strip symbols from the binary.
opt-level = "z" # Optimize for size.
lto = true
codegen-units = 1

View File

@ -1,18 +1,18 @@
#!/bin/bash
SLEEP=${1:-'10'}
EXIT=${2:-'0'}
SLEEP=${1:-10}
EXIT=${2:-0}
echo "Sleeping for $SLEEP seconds...";
sleep $SLEEP;
sleep "$SLEEP";
echo "Waking up!";
if [ $EXIT > 0 ]; then
if [ "$EXIT" -gt "0" ]; then
>&2 echo "Exiting with error code $EXIT";
else
echo "Exiting with error code $EXIT";
fi
exit $EXIT;
exit "$EXIT";

1444
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,30 +0,0 @@
{
"name": "kr",
"version": "1.0.0",
"description": "",
"bin": "src/index.js",
"main": "src/index.js",
"scripts": {
"build": "pkg package.json",
"copy-local": "cp -f out/kr-linux ~/bin/kr",
"build-local": "npm run build && npm run copy-local"
},
"pkg": {
"scripts": "src/**/*.js",
"targets": [
"node18-linux-x64",
"node18-macos-x64",
"node18-win-x64"
],
"outputPath": "out"
},
"author": "Ian Wijma",
"dependencies": {
"pkg": "^5.8.1",
"yargs": "^17.7.2"
},
"devDependencies": {
"@types/node": "^20.8.10",
"@types/yargs": "^17.0.29"
}
}

View File

@ -1,123 +0,0 @@
const Yargs = require('yargs');
const {spawn} = require("child_process");
const yargs = Yargs(process.argv.splice(2))
.scriptName('kr')
.usage('kr [args] <process-to-run>')
.help()
.option('_rpm', {
default: 0,
description: 'The amount of Retries Per Minute, before we stop retrying',
type: 'number'
})
.option('_rph', {
default: 0,
description: 'The amount of Retries Per Hour, before we stop retrying',
type: 'number'
})
.option('_delay', {
default: 0,
description: 'Time in seconds we want to delay the restart with.',
type: 'number'
})
const { _ = [], _rpm: rpm, _rph: rph, _delay: delay } = yargs.argv;
const [command, ...args ] = _;
if (!command) {
return yargs.showHelp();
}
const SECONDS_IN_A_MINUTE = 60;
const SECONDS_IN_A_HOUR = 60 * 60;
let historyMax = 4;
let seconds = SECONDS_IN_A_MINUTE;
let restartName = 'minute';
if (rpm && rph) {
return console.error('Currently, can not define both --rpm and --rph, please choose only one.');
} else if (rpm) {
historyMax = rpm
} else if (rph) {
historyMax = rph
seconds = SECONDS_IN_A_HOUR
restartName = 'hour'
}
/** @type {Object<number, string>} */
const history = {};
const getNow = () => Math.ceil(Date.now() / 1000); // Time in seconds
/** @param {string} logs */
const pushHistory = (logs) => history[getNow() + seconds] = logs;
const updateHistory = () => {
const now = getNow();
const clearKeys = Object.keys(history)
.filter((time) => time <= now);
clearKeys.forEach((key) => delete history[key]);
}
const checkHistory = () => Object.keys(history).length <= historyMax;
const restart = () => setTimeout(() => runCommand(delay), delay * 1000);
const spawnCommand = () => {
const [ spawnCommand, ...cmdArgs ] = command.split(' ');
const spawnArgs = [...cmdArgs, ...args];
return spawn(spawnCommand, spawnArgs);
}
const runCommand = () => {
const runner = spawnCommand();
const commandLogs = [];
const pushLog = (logEntry) => commandLogs.push(logEntry);
const trimLog = () => {
if(commandLogs.length > 5000) commandLogs.shift()
};
const clearLogs = () => commandLogs.length = 0;
const getLogs = () => commandLogs.join('\n');
const handleOut = (type, message) => {
pushLog(`[${type}]\t${message}`);
trimLog();
};
const handleExit = (exitCode) => {
if (exitCode === 0) {
console.log(`Exit code: ${exitCode}`);
} else{
console.log(`[CRASH] exit code: ${exitCode}`)
pushHistory(getLogs());
clearLogs();
updateHistory();
if (checkHistory()) {
console.log('Restarting...');
restart();
} else {
console.error(`The process crashed more then ${historyMax} times in the past ${restartName}, stop retrying.`);
console.error(`See below the last crash log:`);
const keys = Object.keys(history);
const lastKey = keys[keys.length-1];
console.log(history[lastKey]);
}
}
}
runner.on('close', (exitCode) => handleExit(exitCode))
runner.stdout.on('data', (data) => console.log(data.toString().trim()))
runner.stdout.on('data', (data) => handleOut('LOG', data.toString().trim()))
runner.stderr.on('data', (data) => handleOut('ERR', data.toString().trim()))
}
// Leggo!~
runCommand();

132
src/main.rs Normal file
View File

@ -0,0 +1,132 @@
use std::process::{Child, Command};
use std::thread::sleep;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use clap::{arg, Parser};
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Arguments {
// The amount of times we retry in a span of a minute, before we stop retrying
#[arg(long, default_value_t = 0, conflicts_with = "per_hour")]
per_minute: u8,
// The amount of times we retry in a span of a hour, before we stop retrying
#[arg(long, default_value_t = 0, conflicts_with = "per_minute")]
per_hour: u8,
// The amount of seconds we want to delay the restart with.
#[arg(long, default_value_t = 0)]
delay: u8,
#[arg()]
command: String
}
const SECONDS_IN_A_MINUTE: u16 = 60;
const SECONDS_IN_A_HOUR: u16 = 60 * 60;
struct Retry {
command: String,
history: Vec<u64>,
max_retries: u8,
timespan: u16,
restart_delay: u8,
restart_name: String
}
fn main() {
let arguments = Arguments::parse();
let mut retry: Retry = Retry {
command: arguments.command,
history: Vec::new(),
max_retries: 4,
timespan: SECONDS_IN_A_MINUTE,
restart_delay: arguments.delay,
restart_name: "minute".to_string(),
};
if arguments.per_minute > 0 {
retry.max_retries = arguments.per_minute;
} else if arguments.per_hour > 0 {
retry.max_retries = arguments.per_hour;
retry.timespan = SECONDS_IN_A_HOUR;
retry.restart_name = "hour".to_string();
}
run_command(&mut retry)
}
fn get_now() -> u64 {
return SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
}
fn push_history(retry: &mut Retry) {
retry.history.push(get_now() + u64::from(retry.timespan));
}
fn update_history(retry: &mut Retry) {
let now = get_now();
let clear_times: Vec<u64> = retry
.history
.iter()
.filter(|&time| time <= &now)
.map(|&time| time)
.collect();
for time in clear_times {
retry.history.retain(|&h| h != time);
}
}
fn check_history(retry: &Retry) -> bool {
return retry.history.len().lt(&usize::from(retry.max_retries))
}
fn spawn_process(retry: &Retry) -> std::io::Result<Child> {
let mut command_parts: Vec<String> = retry
.command
.split_whitespace()
.map(|s| s.to_string())
.collect();
let mut command: Command = Command::new(&command_parts[0]);
command_parts.remove(0);
command.args(command_parts);
return command.spawn();
}
fn run_command(retry: &mut Retry) {
let mut process = spawn_process(retry)
.expect("Process failed on startup");
let exit_code = match process.wait() {
Ok(exit_code) => exit_code,
Err(err) => {
eprintln!("Failed to wait for process: {}", err);
return;
}
};
if exit_code.success() {
println!("Exit code: {}", exit_code);
} else {
println!("[CRASH] exit code: {}", exit_code);
push_history(retry);
update_history(retry);
if check_history(retry) {
println!("Restarting...");
restart(retry);
} else {
print!("The process has crashed more then {} times in the past {}, stop restarting\n", retry.max_retries, retry.restart_name);
}
}
}
fn restart(retry: &mut Retry) {
sleep(Duration::from_secs(u64::from(retry.restart_delay)));
run_command(retry);
}