diff --git a/Cargo.lock b/Cargo.lock index e65527d1..6108bfe3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -190,6 +190,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + [[package]] name = "downcast-rs" version = "1.2.1" @@ -360,9 +369,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "linutil_core" @@ -390,6 +399,7 @@ dependencies = [ "ratatui", "temp-dir", "textwrap", + "time", "tree-sitter-bash", "tree-sitter-highlight", "tui-term", @@ -486,6 +496,21 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -554,6 +579,12 @@ dependencies = [ "winreg", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -672,9 +703,9 @@ checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" dependencies = [ "bitflags 2.6.0", "errno", @@ -916,6 +947,39 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "toml" version = "0.8.19" diff --git a/core/tabs/security/tab_data.toml b/core/tabs/security/tab_data.toml index ce8d7ac4..fe2c2818 100644 --- a/core/tabs/security/tab_data.toml +++ b/core/tabs/security/tab_data.toml @@ -5,3 +5,8 @@ name = "Firewall Baselines (CTT)" description = "Developed to ease iptables firewall configuration, UFW provides a user friendly way to create an IPv4 or IPv6 host-based firewall. This command installs UFW and configures UFW based on CTT's recommended rules. For more information visit: https://christitus.com/linux-security-mistakes" script = "firewall-baselines.sh" task_list = "I SS" + +[[data.preconditions]] +matches = false +data = "command_exists" +values = [ "firewalld" ] diff --git a/core/tabs/system-setup/fedora/multimedia-codecs.sh b/core/tabs/system-setup/fedora/multimedia-codecs.sh index 751e4547..5711f0d6 100644 --- a/core/tabs/system-setup/fedora/multimedia-codecs.sh +++ b/core/tabs/system-setup/fedora/multimedia-codecs.sh @@ -8,8 +8,6 @@ multimedia() { if [ -e /etc/yum.repos.d/rpmfusion-free.repo ] && [ -e /etc/yum.repos.d/rpmfusion-nonfree.repo ]; then printf "%b\n" "${YELLOW}Installing Multimedia Codecs...${RC}" "$ESCALATION_TOOL" "$PACKAGER" swap ffmpeg-free ffmpeg --allowerasing -y - "$ESCALATION_TOOL" "$PACKAGER" update @multimedia --setopt="install_weak_deps=False" --exclude=PackageKit-gstreamer-plugin -y - "$ESCALATION_TOOL" "$PACKAGER" update @sound-and-video -y printf "%b\n" "${GREEN}Multimedia Codecs Installed...${RC}" else printf "%b\n" "${RED}RPM Fusion repositories not found. Please set up RPM Fusion first!${RC}" diff --git a/core/tabs/system-setup/gaming-setup.sh b/core/tabs/system-setup/gaming-setup.sh index 86bc9f5d..ecd97c54 100755 --- a/core/tabs/system-setup/gaming-setup.sh +++ b/core/tabs/system-setup/gaming-setup.sh @@ -1,10 +1,11 @@ #!/bin/sh -e +# shellcheck disable=SC2086 + . ../common-script.sh installDepend() { - # Check for dependencies - DEPENDENCIES='wine dbus' + DEPENDENCIES='wine dbus git' printf "%b\n" "${YELLOW}Installing dependencies...${RC}" case "$PACKAGER" in pacman) @@ -25,13 +26,16 @@ installDepend() { $AUR_HELPER -S --needed --noconfirm $DEPENDENCIES $DISTRO_DEPS ;; - apt-get|nala) + apt-get | nala) DISTRO_DEPS="libasound2-plugins:i386 libsdl2-2.0-0:i386 libdbus-1-3:i386 libsqlite3-0:i386 wine64 wine32" - "$ESCALATION_TOOL" "$PACKAGER" update "$ESCALATION_TOOL" dpkg --add-architecture i386 - "$ESCALATION_TOOL" "$PACKAGER" install -y software-properties-common - "$ESCALATION_TOOL" apt-add-repository contrib -y + + if [ "$DTYPE" != "pop" ]; then + "$ESCALATION_TOOL" "$PACKAGER" install -y software-properties-common + "$ESCALATION_TOOL" apt-add-repository contrib -y + fi + "$ESCALATION_TOOL" "$PACKAGER" update "$ESCALATION_TOOL" "$PACKAGER" install -y $DEPENDENCIES $DISTRO_DEPS ;; @@ -62,7 +66,7 @@ installAdditionalDepend() { DISTRO_DEPS='steam lutris goverlay' "$ESCALATION_TOOL" "$PACKAGER" -S --needed --noconfirm $DISTRO_DEPS ;; - apt-get|nala) + apt-get | nala) version=$(git -c 'versionsort.suffix=-' ls-remote --tags --sort='v:refname' https://github.com/lutris/lutris | grep -v 'beta' | tail -n1 | @@ -72,7 +76,7 @@ installAdditionalDepend() { curl -sSLo "lutris_${version_no_v}_all.deb" "https://github.com/lutris/lutris/releases/download/${version}/lutris_${version_no_v}_all.deb" printf "%b\n" "${YELLOW}Installing Lutris...${RC}" - "$ESCALATION_TOOL" "$PACKAGER" install ./lutris_"${version_no_v}"_all.deb + "$ESCALATION_TOOL" "$PACKAGER" install -y ./lutris_"${version_no_v}"_all.deb rm lutris_"${version_no_v}"_all.deb @@ -91,7 +95,6 @@ installAdditionalDepend() { "$ESCALATION_TOOL" "$PACKAGER" install -y $DISTRO_DEPS ;; zypper) - # Flatpak DISTRO_DEPS='lutris' "$ESCALATION_TOOL" "$PACKAGER" -n install $DISTRO_DEPS ;; diff --git a/core/tabs/system-setup/system-cleanup.sh b/core/tabs/system-setup/system-cleanup.sh index 573751a0..dccbcf3a 100755 --- a/core/tabs/system-setup/system-cleanup.sh +++ b/core/tabs/system-setup/system-cleanup.sh @@ -22,7 +22,7 @@ cleanup_system() { ;; pacman) "$ESCALATION_TOOL" "$PACKAGER" -Sc --noconfirm - "$ESCALATION_TOOL" "$PACKAGER" -Rns $(pacman -Qtdq) --noconfirm > /dev/null 2>&1 + "$ESCALATION_TOOL" "$PACKAGER" -Rns $(pacman -Qtdq) --noconfirm > /dev/null || true ;; apk) "$ESCALATION_TOOL" "$PACKAGER" cache clean diff --git a/core/tabs/system-setup/system-update.sh b/core/tabs/system-setup/system-update.sh index 53f3a1df..9052ad80 100755 --- a/core/tabs/system-setup/system-update.sh +++ b/core/tabs/system-setup/system-update.sh @@ -5,83 +5,85 @@ fastUpdate() { case "$PACKAGER" in pacman) - - $AUR_HELPER -S --needed --noconfirm rate-mirrors-bin + "$AUR_HELPER" -S --needed --noconfirm rate-mirrors-bin printf "%b\n" "${YELLOW}Generating a new list of mirrors using rate-mirrors. This process may take a few seconds...${RC}" - if [ -s /etc/pacman.d/mirrorlist ]; then + if [ -s "/etc/pacman.d/mirrorlist" ]; then "$ESCALATION_TOOL" cp /etc/pacman.d/mirrorlist /etc/pacman.d/mirrorlist.bak fi # If for some reason DTYPE is still unknown use always arch so the rate-mirrors does not fail - dtype_local=${DTYPE} - if [ "${DTYPE}" = "unknown" ]; then + dtype_local="$DTYPE" + if [ "$dtype_local" = "unknown" ]; then dtype_local="arch" fi - "$ESCALATION_TOOL" rate-mirrors --top-mirrors-number-to-retest=5 --disable-comments --save /etc/pacman.d/mirrorlist --allow-root ${dtype_local} - if [ $? -ne 0 ] || [ ! -s /etc/pacman.d/mirrorlist ]; then + if ! "$ESCALATION_TOOL" rate-mirrors --top-mirrors-number-to-retest=5 --disable-comments --save /etc/pacman.d/mirrorlist --allow-root "$dtype_local" > /dev/null || [ ! -s "/etc/pacman.d/mirrorlist" ]; then printf "%b\n" "${RED}Rate-mirrors failed, restoring backup.${RC}" "$ESCALATION_TOOL" cp /etc/pacman.d/mirrorlist.bak /etc/pacman.d/mirrorlist fi ;; - apt-get|nala) - "$ESCALATION_TOOL" apt-get update - if ! command_exists nala; then - "$ESCALATION_TOOL" apt-get install -y nala || { printf "%b\n" "${YELLOW}Falling back to apt-get${RC}"; PACKAGER="apt-get"; } + if [ "$PACKAGER" = "apt-get" ]; then + printf "%b\n" "${YELLOW}Installing nala for faster updates.${RC}" + "$ESCALATION_TOOL" "$PACKAGER" update + if "$ESCALATION_TOOL" "$PACKAGER" install -y nala; then + PACKAGER="nala"; + printf "%b\n" "${CYAN}Using $PACKAGER as package manager${RC}" + else + printf "%b\n" "${RED}Nala installation failed.${RC}" + printf "%b\n" "${YELLOW}Falling back to apt-get.${RC}" + fi fi if [ "$PACKAGER" = "nala" ]; then - "$ESCALATION_TOOL" cp /etc/apt/sources.list /etc/apt/sources.list.bak - "$ESCALATION_TOOL" nala update - PACKAGER="nala" + if [ -f "/etc/apt/sources.list.d/nala-sources.list" ]; then + "$ESCALATION_TOOL" cp /etc/apt/sources.list.d/nala-sources.list /etc/apt/sources.list.d/nala-sources.list.bak + fi + if ! "$ESCALATION_TOOL" nala fetch --auto -y || [ ! -s "/etc/apt/sources.list.d/nala-sources.list" ]; then + printf "%b\n" "${RED}Nala fetch failed, restoring backup.${RC}" + "$ESCALATION_TOOL" cp /etc/apt/sources.list.d/nala-sources.list.bak /etc/apt/sources.list.d/nala-sources.list + fi fi - - "$ESCALATION_TOOL" "$PACKAGER" upgrade -y ;; dnf) "$ESCALATION_TOOL" "$PACKAGER" update -y ;; zypper) "$ESCALATION_TOOL" "$PACKAGER" ref - "$ESCALATION_TOOL" "$PACKAGER" --non-interactive dup ;; apk) "$ESCALATION_TOOL" "$PACKAGER" update ;; *) - printf "%b\n" "${RED}Unsupported package manager: "$PACKAGER"${RC}" + printf "%b\n" "${RED}Unsupported package manager: ${PACKAGER}${RC}" exit 1 ;; esac } updateSystem() { - printf "%b\n" "${GREEN}Updating system${RC}" + printf "%b\n" "${YELLOW}Updating system packages.${RC}" case "$PACKAGER" in apt-get|nala) - "$ESCALATION_TOOL" "$PACKAGER" update "$ESCALATION_TOOL" "$PACKAGER" upgrade -y ;; dnf) - "$ESCALATION_TOOL" "$PACKAGER" update -y "$ESCALATION_TOOL" "$PACKAGER" upgrade -y ;; pacman) "$ESCALATION_TOOL" "$PACKAGER" -Sy --noconfirm --needed archlinux-keyring - "$ESCALATION_TOOL" "$PACKAGER" -Su --noconfirm + "$AUR_HELPER" -Su --noconfirm ;; zypper) - "$ESCALATION_TOOL" "$PACKAGER" ref "$ESCALATION_TOOL" "$PACKAGER" --non-interactive dup ;; apk) "$ESCALATION_TOOL" "$PACKAGER" upgrade ;; *) - printf "%b\n" "${RED}Unsupported package manager: "$PACKAGER"${RC}" + printf "%b\n" "${RED}Unsupported package manager: ${PACKAGER}${RC}" exit 1 ;; esac @@ -89,18 +91,8 @@ updateSystem() { updateFlatpaks() { if command_exists flatpak; then - printf "%b\n" "${YELLOW}Updating installed Flathub apps...${RC}" - installed_apps=$(flatpak list --app --columns=application) - - if [ -z "$installed_apps" ]; then - printf "%b\n" "${RED}No Flathub apps are installed.${RC}" - return - fi - - for app in $installed_apps; do - printf "%b\n" "${YELLOW}Updating $app...${RC}" - flatpak update -y "$app" - done + printf "%b\n" "${YELLOW}Updating flatpak packages.${RC}" + flatpak update -y fi } diff --git a/core/tabs/utils/power-profile.sh b/core/tabs/utils/power-profile.sh index 1efdadcb..9c242c4d 100644 --- a/core/tabs/utils/power-profile.sh +++ b/core/tabs/utils/power-profile.sh @@ -48,7 +48,7 @@ configureAutoCpufreq() { if command_exists auto-cpufreq; then # Check if the system has a battery to determine if it's a laptop - if [ -d /sys/class/power_supply/BAT0 ]; then + if ls /sys/class/power_supply/BAT* >/dev/null 2>&1; then printf "%b\n" "${GREEN}System detected as laptop. Updating auto-cpufreq for laptop...${RC}" "$ESCALATION_TOOL" auto-cpufreq --force powersave else diff --git a/docs/roadmap.md b/docs/roadmap.md index 71a7aaf5..2f6c7a9b 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -14,7 +14,7 @@ ### Q4 2024 - [x] Finish the foundation of the project's CLI - [x] Implement CLI arguments and configuration support -- [ ] Add an option for logging script executions +- [x] Add an option for logging script executions ### Q1 2025 - [ ] GUI Brainstorming and Planning diff --git a/tui/Cargo.toml b/tui/Cargo.toml index 6da35c56..401edc08 100644 --- a/tui/Cargo.toml +++ b/tui/Cargo.toml @@ -20,9 +20,10 @@ portable-pty = "0.8.1" ratatui = { version = "0.29.0", features = ["crossterm"], default-features = false } tui-term = "0.2.0" temp-dir = "0.1.14" +time = { version = "0.3.36", features = ["formatting", "local-offset", "macros"], default-features = false } unicode-width = { version = "0.2.0", default-features = false } rand = { version = "0.8.5", optional = true } -linutil_core = { path = "../core", version = "24.9.28" } +linutil_core = { version = "24.9.28", path = "../core" } tree-sitter-highlight = "0.24.3" tree-sitter-bash = "0.23.1" textwrap = { version = "0.16.1", default-features = false } diff --git a/tui/src/running_command.rs b/tui/src/running_command.rs index 798c67c0..779a0b3c 100644 --- a/tui/src/running_command.rs +++ b/tui/src/running_command.rs @@ -17,11 +17,11 @@ use std::{ sync::{Arc, Mutex}, thread::JoinHandle, }; +use time::{macros::format_description, OffsetDateTime}; use tui_term::{ vt100::{self, Screen}, widget::PseudoTerminal, }; - pub struct RunningCommand { /// A buffer to save all the command output (accumulates, until the command exits) buffer: Arc>>, @@ -37,6 +37,7 @@ pub struct RunningCommand { writer: Box, /// Only set after the process has ended status: Option, + log_path: Option, scroll_offset: usize, } @@ -79,10 +80,20 @@ impl FloatContent for RunningCommand { .style(Style::default()), ); - Block::default() + let mut block = Block::default() .borders(Borders::ALL) .border_set(ratatui::symbols::border::ROUNDED) - .title_top(title_line.centered()) + .title_top(title_line.centered()); + + if let Some(log_path) = &self.log_path { + block = + block.title_bottom(Line::from(format!(" Log saved: {} ", log_path)).centered()); + } else { + block = + block.title_bottom(Line::from(" Press 'l' to save command log ").centered()); + } + + block }; // Process the buffer and create the pseudo-terminal widget @@ -111,6 +122,11 @@ impl FloatContent for RunningCommand { KeyCode::PageDown => { self.scroll_offset = self.scroll_offset.saturating_sub(10); } + KeyCode::Char('l') if self.is_finished() => { + if let Ok(log_path) = self.save_log() { + self.log_path = Some(log_path); + } + } // Pass other key events to the terminal _ => self.handle_passthrough_key_event(key), } @@ -134,6 +150,7 @@ impl FloatContent for RunningCommand { Shortcut::new("Close window", ["Enter", "q"]), Shortcut::new("Scroll up", ["Page up"]), Shortcut::new("Scroll down", ["Page down"]), + Shortcut::new("Save log", ["l"]), ]), ) } else { @@ -237,6 +254,7 @@ impl RunningCommand { pty_master: pair.master, writer, status: None, + log_path: None, scroll_offset: 0, } } @@ -284,6 +302,24 @@ impl RunningCommand { } } + fn save_log(&self) -> std::io::Result { + let mut log_path = std::env::temp_dir(); + let date_format = format_description!("[year]-[month]-[day]-[hour]-[minute]-[second]"); + log_path.push(format!( + "linutil_log_{}.log", + OffsetDateTime::now_local() + .unwrap_or(OffsetDateTime::now_utc()) + .format(&date_format) + .unwrap() + )); + + let mut file = std::fs::File::create(&log_path)?; + let buffer = self.buffer.lock().unwrap(); + file.write_all(&buffer)?; + + Ok(log_path.to_string_lossy().into_owned()) + } + /// Convert the KeyEvent to pty key codes, and send them to the virtual terminal fn handle_passthrough_key_event(&mut self, key: &KeyEvent) { let input_bytes = match key.code {