update zsh install

This commit is contained in:
Chris Titus 2024-08-08 15:54:37 -05:00
commit e58c54ad04
37 changed files with 874 additions and 441 deletions

View File

@ -6,7 +6,7 @@
## Type of Change
- [ ] New feature
- [ ] Bug fix
- [ ] Documentation Update (Due to be added!)
- [ ] Documentation Update
- [ ] Refactoring
- [ ] Hotfix
- [ ] Security patch

View File

@ -8,3 +8,8 @@ updates:
directory: "/"
schedule:
interval: "weekly"
ignore:
- dependency-name: "actions/stale"
versions: '>= 9'
- dependency-name: "actions/setup-python"
versions: '> 4'

22
.github/workflows/github-pages.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: GitHub Pages Deploy
on:
release:
types: [published, prereleased]
workflow_dispatch:
permissions:
contents: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: 3.x
- uses: actions/cache@v4
with:
key: ${{ github.ref }}
path: .cache
- run: pip install mkdocs-material
- run: pip install pillow cairosvg
- run: mkdocs gh-deploy --force

View File

@ -0,0 +1,45 @@
name: Close issue on /close
on:
issue_comment:
types: [created, edited]
jobs:
closeIssueOnClose:
# Skip this job if the comment was created/edited on a PR
if: ${{ !github.event.issue.pull_request }}
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: none
contents: read
steps:
- name: Check for /close comment
id: check_comment
run: |
if [[ "${{ contains(github.event.comment.body, '/close') }}" == "true" ]]; then
echo "comment=true" >> $GITHUB_ENV
else
echo "comment=false" >> $GITHUB_ENV
fi
- name: Check if the user is allowed
id: check_user
if: env.comment == 'true'
run: |
ALLOWED_USERS=("ChrisTitusTech" "afonsofrancof" "Marterich" "MyDrift-user" "Real-MullaC")
if [[ " ${ALLOWED_USERS[@]} " =~ " ${{ github.event.comment.user.login }} " ]]; then
echo "user=true" >> $GITHUB_ENV
else
echo "user=false" >> $GITHUB_ENV
fi
- name: Close issue if conditions are met
if: env.comment == 'true' && env.user == 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
run: |
echo Closing the issue...
gh issue close $ISSUE_NUMBER --repo ${{ github.repository }}

View File

@ -2,7 +2,7 @@ name: LinUtil Release
on:
push:
branches: [ "main" ]
branches: ["main"]
permissions:
contents: write
@ -16,25 +16,40 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Cache Cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-registry-
- name: Cache Cargo index
uses: actions/cache@v4
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-index-
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Build
run: cargo build --target-dir=build --release --verbose
- uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: Commit Linutil
file_pattern: 'build/release/linutil'
if: success()
- uses: actions/checkout@v4
- name: Cache Cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-registry-
- name: Cache Cargo index
uses: actions/cache@v4
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-index-
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-unknown-linux-musl
- name: Install cross-rs for cross-compilation
run: cargo install cross
- name: Build x86_64 binary
run: cargo build --target-dir=build --release --verbose --target=x86_64-unknown-linux-musl
- name: Build aarch64 binary
run: cross build --target-dir=build --release --verbose --target=aarch64-unknown-linux-musl
- name: Move binaries to build directory
run: |
mv build/x86_64-unknown-linux-musl/release/linutil build/linutil
mv build/aarch64-unknown-linux-musl/release/linutil build/linutil-aarch64
- name: Pull latest changes
run: |
git config --global user.email "github-actions@github.com"
git config --global user.name "GitHub Actions"
git pull origin main
- uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: Commit Linutil
file_pattern: "build/linutil build/linutil-aarch64"
if: success()

View File

@ -1,8 +1,8 @@
name: Pre-Release LinUtil
permissions:
contents: write # Grant write permissions to contents
packages: write # Grant write permissions to packages
contents: write # Grant write permissions to contents
packages: write # Grant write permissions to packages
on:
workflow_dispatch: # Manual trigger added
@ -30,7 +30,8 @@ jobs:
body: "![GitHub Downloads (specific asset, specific tag)](https://img.shields.io/github/downloads/ChrisTitusTech/linutil/${{ env.version }}/linutil)"
append_body: false
files: |
./build/release/linutil
./build/linutil
./build/linutil-aarch64
prerelease: true
generate_release_notes: true
env:

View File

@ -2,53 +2,39 @@ name: Rust Checks
on:
pull_request:
branches: [ "main" ]
branches: ["main"]
env:
CARGO_TERM_COLOR: always
jobs:
cargo_check:
name: Cargo Check
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Cache Cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-registry-
- name: Cache Cargo index
uses: actions/cache@v4
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-index-
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Build
run: cargo check
lints:
name: Lints
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Checkout sources
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Run cargo fmt
run: cargo fmt --all --check
- name: Cache Cargo registry
uses: actions/cache@v4
with:
path: ~/.cargo/registry
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-registry-
- name: Run cargo clippy
run: cargo clippy
- name: Cache Cargo index
uses: actions/cache@v4
with:
path: ~/.cargo/git
key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
restore-keys: ${{ runner.os }}-cargo-index-
- name: Run cargo clippy
run: cargo clippy
- name: Run cargo fmt
run: cargo fmt --all --check

95
Cargo.lock generated
View File

@ -163,9 +163,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.9"
version = "4.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462"
checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3"
dependencies = [
"clap_builder",
"clap_derive",
@ -173,9 +173,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.9"
version = "4.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942"
checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa"
dependencies = [
"anstream",
"anstyle",
@ -185,9 +185,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.5.8"
version = "4.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085"
checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e"
dependencies = [
"heck",
"proc-macro2",
@ -280,6 +280,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "hashbrown"
version = "0.14.5"
@ -319,6 +325,25 @@ dependencies = [
"cc",
]
[[package]]
name = "include_dir"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd"
dependencies = [
"include_dir_macros",
]
[[package]]
name = "include_dir_macros"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "ioctl-rs"
version = "0.1.6"
@ -525,6 +550,34 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
dependencies = [
"fuchsia-cprng",
"libc",
"rand_core 0.3.1",
"rdrand",
"winapi",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "ratatui"
version = "0.27.0"
@ -546,6 +599,15 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "redox_syscall"
version = "0.5.1"
@ -555,6 +617,15 @@ dependencies = [
"bitflags 2.5.0",
]
[[package]]
name = "remove_dir_all"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [
"winapi",
]
[[package]]
name = "rustversion"
version = "1.0.17"
@ -722,6 +793,16 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "tempdir"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
dependencies = [
"rand",
"remove_dir_all",
]
[[package]]
name = "termios"
version = "0.2.2"
@ -759,9 +840,11 @@ dependencies = [
"clap",
"crossterm",
"ego-tree",
"include_dir",
"oneshot",
"portable-pty",
"ratatui",
"tempdir",
"tui-term",
]

View File

@ -5,13 +5,15 @@ edition = "2021"
[dependencies]
chrono = "0.4.33"
clap = { version = "4.5.9", features = ["derive"] }
clap = { version = "4.5.11", features = ["derive"] }
crossterm = "0.27.0"
ego-tree = "0.6.2"
oneshot = "0.1.8"
portable-pty = "0.8.1"
ratatui = "0.27.0"
tui-term = "0.1.12"
include_dir = "0.7.4"
tempdir = "0.3.7"
[[bin]]
name = "linutil"

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Chris Titus
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,21 +1,29 @@
# Chris Titus Tech's Linux Utility
[![Version](https://img.shields.io/github/v/release/ChrisTitusTech/linutil?color=%230567ff&label=Latest%20Release&style=for-the-badge)](https://github.com/ChrisTitusTech/linutil/releases/latest)
![GitHub Downloads (specific asset, all releases)](https://img.shields.io/github/downloads/ChrisTitusTech/linutil/linutil?label=Total%20Downloads&style=for-the-badge)
![Preview](docs/assets/preview.png)
A distro-agnostic* toolbox which helps with everyday Linux tasks. It can help you set up applications and your system for specific use cases! Written with Rust 🦀
\* — The project is in active development, so there could be some issues. Please consider [submitting feedback](https://github.com/ChrisTitusTech/linutil/issues).
## 💡 Usage
Open your terminal and paste this command
Open your terminal and paste this command:
```bash
curl -fsSL https://christitus.com/linux | sh
```
## 🎓 Documentation
### [LinUtil Official Documentation](https://christitustech.github.io/linutil/)
## 💖 Support
- To morally and mentally support the project, make sure to leave a ⭐️!
To morally and mentally support the project, make sure to leave a ⭐️!
## 🏅 Thanks to all Contributors
Thanks a lot for spending your time helping Winutil grow. Thanks a lot! Keep rocking 🍻.
Thanks a lot for spending your time helping Linutil grow. Keep rocking 🍻.
[![Contributors](https://contrib.rocks/image?repo=ChrisTitusTech/linutil)](https://github.com/ChrisTitusTech/linutil/graphs/contributors)

Binary file not shown.

BIN
build/linutil Executable file

Binary file not shown.

BIN
build/linutil-aarch64 Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,11 +1,6 @@
#!/bin/sh -e
checkEnv() {
checkCommandRequirements 'curl groups sudo'
checkPackageManager 'apt-get dnf pacman zypper'
checkSuperUser
checkDistro
}
. ./common-script.sh
setupAlacritty() {
echo "Install Alacritty if not already installed..."

View File

@ -0,0 +1,27 @@
#!/bin/sh -e
. ./common-script.sh
makeDWM(){
cd $HOME && git clone https://github.com/ChrisTitusTech/dwm-titus.git # CD to Home directory to install dwm-titus
# This path can be changed (e.g. to linux-toolbox directory)
cd dwm-titus/ # Hardcoded path, maybe not the best.
sudo ./setup.sh # Run setup
sudo make clean install # Run make clean install
}
setupDWM() {
echo "Installing DWM-Titus if not already installed"
case "$PACKAGER" in # Install pre-Requisites
pacman)
sudo "$PACKAGER" -S --noconfirm --needed base-devel libx11 libxinerama libxft imlib2
;;
*)
sudo "$PACKAGER" install -y build-essential libx11-dev libxinerama-dev libxft-dev libimlib2-dev
;;
esac
}
checkEnv
setupDWM
makeDWM

View File

@ -1,11 +1,6 @@
#!/bin/sh -e
checkEnv() {
checkCommandRequirements 'curl groups sudo'
checkPackageManager 'apt-get dnf pacman zypper'
checkSuperUser
checkDistro
}
. ./common-script.sh
setupKitty() {
echo "Install Kitty if not already installed..."

View File

@ -1,11 +1,6 @@
#!/bin/sh -e
checkEnv() {
checkCommandRequirements 'curl groups sudo'
checkPackageManager 'apt-get dnf pacman zypper'
checkSuperUser
checkDistro
}
. ./common-script.sh
setupRofi() {
echo "Install Rofi if not already installed..."

View File

@ -0,0 +1,51 @@
#!/bin/sh
. ./common-script.sh
# Function to install zsh
install_zsh() {
echo "Install ZSH if not already installed..."
if ! command_exists zsh; then
case "$PACKAGER" in
pacman)
sudo "$PACKAGER" -S --noconfirm zsh
;;
*)
sudo "$PACKAGER" install -y zsh
;;
esac
else
echo "ZSH is already installed."
fi
}
# Function to setup zsh configuration
setup_zsh_config() {
CONFIG_DIR="$HOME/.config/zsh"
ZSHRC_FILE="$CONFIG_DIR/.zshrc"
if [ ! -d "$CONFIG_DIR" ]; then
mkdir -p "$CONFIG_DIR"
fi
# Write the configuration to .zshrc
cat <<EOL >"$ZSHRC_FILE"
HISTFILE=~/.config/zsh/.histfile
HISTSIZE=5000
SAVEHIST=100000
setopt autocd extendedglob
unsetopt beep
bindkey -v
# Configure the prompt with embedded Solarized color codes
PROMPT='%F{32}%n%f%F{166}@%f%F{64}%m:%F{166}%~%f%F{15}$%f '
RPROMPT='%F{15}(%F{166}%D{%H:%M}%F{15})%f'
EOL
# Ensure /etc/zsh/zshenv sets ZDOTDIR to the user's config directory
echo 'export ZDOTDIR="$HOME/.config/zsh"' | sudo tee -a /etc/zsh/zshenv
}
checkEnv
setup_zsh_config
install_zsh

View File

@ -78,7 +78,8 @@ checkDistro() {
checkEnv() {
checkCommandRequirements 'curl groups sudo'
checkPackageManager 'apt-get dnf pacman zypper'
checkPackageManager 'apt-get nala dnf pacman zypper yum xbps-install nix-env'
checkCurrentDirectoryWritable
checkSuperUser
checkDistro
}

View File

@ -1,59 +0,0 @@
#!/bin/sh
# Function to install zsh
install_zsh() {
if command -v apt-get >/dev/null 2>&1; then
sudo apt-get update
sudo apt-get install -y zsh
elif command -v yum >/dev/null 2>&1; then
sudo yum install -y zsh
elif command -v dnf >/dev/null 2>&1; then
sudo dnf install -y zsh
elif command -v pacman >/dev/null 2>&1; then
sudo pacman -Syu zsh
elif command -v zypper >/dev/null 2>&1; then
sudo zypper install -y zsh
else
echo "No compatible package manager found. Please install zsh manually." >&2
exit 1
fi
}
# Function to setup zsh configuration
setup_zsh_config() {
CONFIG_DIR="$HOME/.config/zsh"
ZSHRC_FILE="$CONFIG_DIR/.zshrc"
# Create config directory if it doesn't exist
mkdir -p "$CONFIG_DIR"
# Write the configuration to .zshrc
cat <<EOL >"$ZSHRC_FILE"
# Lines configured by zsh-newuser-install
HISTFILE=~/.config/zsh/.histfile
HISTSIZE=5000
SAVEHIST=100000
setopt autocd extendedglob
unsetopt beep
bindkey -v
# End of lines configured by zsh-newuser-install
# Configure the prompt with embedded Solarized color codes
PROMPT='%F{32}%n%f%F{166}@%f%F{64}%m:%F{166}%~%f%F{15}$%f '
RPROMPT='%F{15}(%F{166}%D{%H:%M}%F{15})%f'
EOL
# Ensure /etc/zsh/zshenv sets ZDOTDIR to the user's config directory
echo 'export ZDOTDIR="$HOME/.config/zsh"' | sudo tee -a /etc/zsh/zshenv
}
# Execute the installation and setup
install_zsh
if [ $? -eq 0 ]; then
echo "zsh installed successfully."
setup_zsh_config
else
echo "zsh installation failed."
exit 1
fi

View File

@ -0,0 +1,39 @@
#!/bin/sh -e
installPkg() {
echo "Install UFW if not already installed..."
if ! command_exists ufw; then
case ${PACKAGER} in
pacman)
sudo "${PACKAGER}" -S --noconfirm ufw
;;
*)
sudo "${PACKAGER}" install -y ufw
;;
esac
else
echo "UFW is already installed."
fi
echo -e "${GREEN}Using Chris Titus Recommended Firewall Rules${RC}"
sudo ufw limit 22/tcp
echo "Limiting port 22/tcp (UFW)"
sudo ufw allow 80/tcp
echo "Allowing port 80/tcp (UFW)"
sudo ufw allow 443/tcp
echo "Allowing port 443/tcp (UFW)"
sudo ufw default deny incoming
echo "Denying Incoming Packets by Default(UFW)"
sudo ufw default allow outgoing
echo "Allowing Outcoming Packets by Default(UFW)"
sudo ufw enable
echo -e "${GREEN}Enabled Firewall with Baselines!${RC}"
}
checkEnv
installPkg

View File

@ -1,5 +1,7 @@
#!/bin/sh -e
. ./common-script.sh
# Check if the home directory and linuxtoolbox folder exist, create them if they don't
LINUXTOOLBOXDIR="$HOME/linuxtoolbox"
@ -22,13 +24,6 @@ fi
cd "$LINUXTOOLBOXDIR/linutil" || exit
checkEnv() {
checkCommandRequirements 'curl groups sudo'
checkPackageHandler 'apt yum dnf pacman zypper'
checkCurrentDirectoryWritable
checkSuperUser
}
installDepend() {
## Check for dependencies.
DEPENDENCIES='tar tree multitail tldr trash-cli unzip cmake make jq'
@ -60,12 +55,12 @@ installDepend() {
fi
"$AUR_HELPER" --noconfirm -S "$DEPENDENCIES"
;;
apt)
apt-get|nala)
COMPILEDEPS='build-essential'
sudo "$PACKAGER" update
sudo dpkg --add-architecture i386
sudo "$PACKAGER" update
sudo "$PACKAGER" install -y "$DEPENDENCIES" $COMPILEDEPS
sudo "$PACKAGER" install -y $DEPENDENCIES $COMPILEDEPS
;;
dnf)
COMPILEDEPS='@development-tools'
@ -81,7 +76,7 @@ installDepend() {
sudo "$PACKAGER" --non-interactive install libgcc_s1-gcc7-32bit glibc-devel-32bit
;;
*)
sudo "$PACKAGER" install -y "$DEPENDENCIES"
sudo "$PACKAGER" install -y $DEPENDENCIES # Fixed bug where no packages found on debian-based
;;
esac
}

View File

@ -1,11 +1,6 @@
#!/bin/sh -e
checkEnv() {
checkCommandRequirements 'curl groups sudo'
checkPackageManager 'apt-get yum dnf pacman zypper'
checkCurrentDirectoryWritable
checkSuperUser
}
. ./common-script.sh
installDepend() {
## Check for dependencies.

View File

@ -1,18 +1,20 @@
#!/bin/sh -e
. ./common-script.sh
# Check if the home directory and linuxtoolbox folder exist, create them if they don't
LINUXTOOLBOXDIR="$HOME/linuxtoolbox"
if [ ! -d "$LINUXTOOLBOXDIR" ]; then
echo -e "${YELLOW}Creating linuxtoolbox directory: $LINUXTOOLBOXDIR${RC}"
printf "${YELLOW}Creating linuxtoolbox directory: %s${RC}\n" "$LINUXTOOLBOXDIR"
mkdir -p "$LINUXTOOLBOXDIR"
echo -e "${GREEN}linuxtoolbox directory created: $LINUXTOOLBOXDIR${RC}"
printf "${GREEN}linuxtoolbox directory created: %s${RC}\n" "$LINUXTOOLBOXDIR"
fi
cd "$LINUXTOOLBOXDIR" || exit
install_theme_tools() {
echo -e "${YELLOW}Installing theme tools (qt6ct and kvantum)...${RC}"
printf "${YELLOW}Installing theme tools (qt6ct and kvantum)...${RC}\n"
case $PACKAGER in
apt-get)
sudo apt-get update
@ -31,14 +33,14 @@ install_theme_tools() {
sudo pacman --noconfirm -S qt6ct kvantum
;;
*)
echo -e "${RED}Unsupported package manager. Please install qt6ct and kvantum manually.${RC}"
printf "${RED}Unsupported package manager. Please install qt6ct and kvantum manually.${RC}\n"
exit 1
;;
esac
}
configure_qt6ct() {
echo -e "${YELLOW}Configuring qt6ct...${RC}"
printf "${YELLOW}Configuring qt6ct...${RC}\n"
mkdir -p "$HOME/.config/qt6ct"
cat <<EOF > "$HOME/.config/qt6ct/qt6ct.conf"
[Appearance]
@ -46,17 +48,26 @@ style=kvantum
color_scheme=default
icon_theme=breeze
EOF
echo -e "${GREEN}qt6ct configured successfully.${RC}"
printf "${GREEN}qt6ct configured successfully.${RC}\n"
# Add QT_QPA_PLATFORMTHEME to /etc/environment
if ! grep -q "QT_QPA_PLATFORMTHEME=qt6ct" /etc/environment; then
printf "${YELLOW}Adding QT_QPA_PLATFORMTHEME to /etc/environment...${RC}\n"
echo "QT_QPA_PLATFORMTHEME=qt6ct" | sudo tee -a /etc/environment > /dev/null
printf "${GREEN}QT_QPA_PLATFORMTHEME added to /etc/environment.${RC}\n"
else
printf "${GREEN}QT_QPA_PLATFORMTHEME already set in /etc/environment.${RC}\n"
fi
}
configure_kvantum() {
echo -e "${YELLOW}Configuring Kvantum...${RC}"
printf "${YELLOW}Configuring Kvantum...${RC}\n"
mkdir -p "$HOME/.config/Kvantum"
cat <<EOF > "$HOME/.config/Kvantum/kvantum.kvconfig"
[General]
theme=Breeze
EOF
echo -e "${GREEN}Kvantum configured successfully.${RC}"
printf "${GREEN}Kvantum configured successfully.${RC}\n"
}
checkEnv

View File

@ -1,11 +1,6 @@
#!/bin/sh -e
checkEnv() {
checkCommandRequirements 'curl groups sudo'
checkPackageManager 'apt-get nala dnf pacman zypper yum xbps-install'
checkSuperUser
checkDistro
}
. ./common-script.sh
fastUpdate() {
case ${PACKAGER} in
@ -83,7 +78,8 @@ updateSystem() {
sudo "${PACKAGER}" upgrade -y
;;
pacman)
sudo "${PACKAGER}" -Syu --noconfirm
sudo "${PACKAGER}" -Sy --noconfirm --needed archlinux-keyring
sudo "${PACKAGER}" -Su --noconfirm
;;
zypper)
sudo ${PACKAGER} ref
@ -99,6 +95,13 @@ updateSystem() {
esac
}
updateFlatpaks() {
if command_exists flatpak; then
flatpak update -y
fi
}
checkEnv
fastUpdate
updateSystem
updateFlatpaks

5
src/commands/test/lib.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/sh
say_hello () {
echo Hi
}

6
src/commands/test/main.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/sh
# The current working directory will always be inside "commands"
. test/lib.sh
say_hello

View File

@ -1,4 +1,4 @@
use crate::{float::floating_window, theme::*};
use crate::{float::floating_window, running_command::Command, state::AppState};
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind};
use ego_tree::{tree, NodeId};
use ratatui::{
@ -9,18 +9,10 @@ use ratatui::{
Frame,
};
macro_rules! with_common_script {
($command:expr) => {
concat!(
include_str!("commands/common-script.sh"),
include_str!($command)
)
};
}
#[derive(Clone)]
struct ListNode {
name: &'static str,
command: &'static str,
command: Command,
}
/// This is a data structure that has everything necessary to draw and manage a menu of commands
@ -37,6 +29,10 @@ pub struct CustomList {
/// This stores the preview windows state. If it is None, it will not be displayed.
/// If it is Some, we show it with the content of the selected item
preview_window_state: Option<PreviewWindowState>,
// This stores the current search query
filter_query: String,
// This stores the filtered tree
filtered_items: Vec<ListNode>,
}
/// This struct stores the preview window state
@ -60,66 +56,72 @@ impl CustomList {
// case the tree! macro expands to `ego-tree::tree` data structure
let tree = tree!(ListNode {
name: "root",
command: ""
command: Command::None,
} => {
ListNode {
name: "Full System Update",
command: with_common_script!("commands/system-update.sh"),
},
ListNode {
name: "Setup Bash Prompt",
command: "bash -c \"$(curl -s https://raw.githubusercontent.com/ChrisTitusTech/mybash/main/setup.sh)\""
},
ListNode {
name: "Setup Neovim",
command: "bash -c \"$(curl -s https://raw.githubusercontent.com/ChrisTitusTech/neovim/main/setup.sh)\""
},
ListNode {
name: "Setup Zsh and Prompt",
command: with_common_script!("commands/install-zsh.sh"),
},
// ListNode {
// name: "Just ls, nothing special, trust me",
// command: include_str!("commands/special_ls.sh"),
// },
ListNode {
name: "System Setup",
command: ""
command: Command::None,
} => {
ListNode {
name: "Build Prerequisites",
command: with_common_script!("commands/system-setup/1-compile-setup.sh"),
command: Command::LocalFile("system-setup/1-compile-setup.sh"),
},
ListNode {
name: "Gaming Dependencies",
command: with_common_script!("commands/system-setup/2-gaming-setup.sh"),
command: Command::LocalFile("system-setup/2-gaming-setup.sh"),
},
ListNode {
name: "Global Theme",
command: with_common_script!("commands/system-setup/3-global-theme.sh"),
command: Command::LocalFile("system-setup/3-global-theme.sh"),
},
},
ListNode {
name: "Security",
command: Command::None
} => {
ListNode {
name: "Recursion?",
command: "cargo run"
name: "Firewall Baselines (CTT)",
command: Command::LocalFile("security/firewall-baselines.sh"),
}
},
ListNode {
name: "Titus Dotfiles",
command: ""
name: "Applications Setup",
command: Command::None
} => {
ListNode {
name: "Alacritty Setup",
command: with_common_script!("commands/dotfiles/alacritty-setup.sh"),
name: "Alacritty",
command: Command::LocalFile("applications-setup/alacritty-setup.sh"),
},
ListNode {
name: "Kitty Setup",
command: with_common_script!("commands/dotfiles/kitty-setup.sh"),
name: "Bash Prompt",
command: Command::Raw("bash -c \"$(curl -s https://raw.githubusercontent.com/ChrisTitusTech/mybash/main/setup.sh)\""),
},
ListNode {
name: "Rofi Setup",
command: with_common_script!("commands/dotfiles/rofi-setup.sh"),
name: "DWM-Titus",
command: Command::LocalFile("applications-setup/dwmtitus-setup.sh")
},
}
ListNode {
name: "Kitty",
command: Command::LocalFile("applications-setup/kitty-setup.sh")
},
ListNode {
name: "Neovim",
command: Command::Raw("bash -c \"$(curl -s https://raw.githubusercontent.com/ChrisTitusTech/neovim/main/setup.sh)\""),
},
ListNode {
name: "Rofi",
command: Command::LocalFile("applications-setup/rofi-setup.sh"),
},
ListNode {
name: "ZSH Prompt",
command: Command::LocalFile("applications-setup/zsh-setup.sh"),
}
},
ListNode {
name: "Full System Update",
command: Command::LocalFile("system-update.sh"),
},
});
// We don't get a reference, but rather an id, because references are siginficantly more
// paintfull to manage
@ -130,46 +132,61 @@ impl CustomList {
list_state: ListState::default().with_selected(Some(0)),
// By default the PreviewWindowState is set to None, so it is not being shown
preview_window_state: None,
filter_query: String::new(),
filtered_items: vec![],
}
}
/// Draw our custom widget to the frame
pub fn draw(&mut self, frame: &mut Frame, area: Rect) {
// Get the last element in the `visit_stack` vec
let theme = get_theme();
let curr = self
.inner_tree
.get(*self.visit_stack.last().unwrap())
.unwrap();
let mut items = vec![];
// If we are not at the root of our filesystem tree, we need to add `..` path, to be able
// to go up the tree
// icons:  
if !self.at_root() {
items.push(Line::from(format!("{} ..", theme.dir_icon)).style(theme.dir_color));
}
// Iterate through all the children
for node in curr.children() {
// The difference between a "directory" and a "command" is simple: if it has children,
// it's a directory and will be handled as such
if node.has_children() {
pub fn draw(&mut self, frame: &mut Frame, area: Rect, state: &AppState) {
let item_list: Vec<Line> = if self.filter_query.is_empty() {
let mut items: Vec<Line> = vec![];
// If we are not at the root of our filesystem tree, we need to add `..` path, to be able
// to go up the tree
// icons:  
if !self.at_root() {
items.push(
Line::from(format!("{} {}", theme.dir_icon, node.value().name))
.style(theme.dir_color),
);
} else {
items.push(
Line::from(format!("{} {}", theme.cmd_icon, node.value().name))
.style(theme.cmd_color),
Line::from(format!("{} ..", state.theme.dir_icon))
.style(state.theme.dir_color),
);
}
}
// Get the last element in the `visit_stack` vec
let curr = self
.inner_tree
.get(*self.visit_stack.last().unwrap())
.unwrap();
// Iterate through all the children
for node in curr.children() {
// The difference between a "directory" and a "command" is simple: if it has children,
// it's a directory and will be handled as such
if node.has_children() {
items.push(
Line::from(format!("{} {}", state.theme.dir_icon, node.value().name))
.style(state.theme.dir_color),
);
} else {
items.push(
Line::from(format!("{} {}", state.theme.cmd_icon, node.value().name))
.style(state.theme.cmd_color),
);
}
}
items
} else {
self.filtered_items
.iter()
.map(|node| {
Line::from(format!("{} {}", state.theme.cmd_icon, node.name))
.style(state.theme.cmd_color)
})
.collect()
};
// create the normal list widget containing only item in our "working directory" / tree
// node
let list = List::new(items)
let list = List::new(item_list)
.highlight_style(Style::default().reversed())
.block(Block::default().borders(Borders::ALL).title(format!(
"Linux Toolbox - {}",
@ -208,8 +225,39 @@ impl CustomList {
}
}
pub fn filter(&mut self, query: String) {
self.filter_query.clone_from(&query);
self.filtered_items.clear();
let query_lower = query.to_lowercase();
let mut stack = vec![self.inner_tree.root().id()];
while let Some(node_id) = stack.pop() {
let node = self.inner_tree.get(node_id).unwrap();
if node.value().name.to_lowercase().contains(&query_lower) && !node.has_children() {
self.filtered_items.push(node.value().clone());
}
for child in node.children() {
stack.push(child.id());
}
}
self.filtered_items.sort_by(|a, b| a.name.cmp(b.name));
}
/// Resets the selection to the first item
pub fn reset_selection(&mut self) {
if !self.filtered_items.is_empty() {
self.list_state.select(Some(0));
} else {
self.list_state.select(None);
}
}
/// Handle key events, we are only interested in `Press` and `Repeat` events
pub fn handle_key(&mut self, event: KeyEvent) -> Option<&'static str> {
pub fn handle_key(&mut self, event: KeyEvent, state: &AppState) -> Option<Command> {
if event.kind == KeyEventKind::Release {
return None;
}
@ -223,7 +271,7 @@ impl CustomList {
return None;
}
self.try_scroll_down();
self.list_state.select_next();
None
}
KeyCode::Char('k') | KeyCode::Up => {
@ -234,19 +282,26 @@ impl CustomList {
return None;
}
self.try_scroll_up();
self.list_state.select_previous();
None
}
// The 'p' key toggles the preview on and off
KeyCode::Char('p') => {
self.toggle_preview_window();
self.toggle_preview_window(state);
None
}
KeyCode::Enter => self.handle_enter(),
KeyCode::Enter => {
if self.preview_window_state.is_none() {
self.handle_enter()
} else {
None
}
}
_ => None,
}
}
fn toggle_preview_window(&mut self) {
fn toggle_preview_window(&mut self, state: &AppState) {
// If the preview window is active, disable it
if self.preview_window_state.is_some() {
self.preview_window_state = None;
@ -255,46 +310,29 @@ impl CustomList {
// Get the selected command
if let Some(selected_command) = self.get_selected_command() {
// If command is a folder, we don't display a preview
if selected_command.is_empty() {
return;
}
// Reconstruct the line breaks and file formatting after the
// 'include_str!()' call in the node
let lines: Vec<String> = selected_command
.lines()
.map(|line| line.to_string())
.collect();
let lines = match selected_command {
Command::Raw(cmd) => {
// Reconstruct the line breaks and file formatting after the
// 'include_str!()' call in the node
cmd.lines().map(|line| line.to_string()).collect()
}
Command::LocalFile(file_path) => {
let mut full_path = state.temp_path.clone();
full_path.push(file_path);
let file_contents = std::fs::read_to_string(&full_path)
.map_err(|_| format!("File not found: {:?}", &full_path))
.unwrap();
file_contents.lines().map(|line| line.to_string()).collect()
}
// If command is a folder, we don't display a preview
Command::None => return,
};
// Show the preview window with the text lines
self.preview_window_state = Some(PreviewWindowState::new(lines));
}
}
}
fn try_scroll_up(&mut self) {
self.list_state
.select(Some(self.list_state.selected().unwrap().saturating_sub(1)));
}
fn try_scroll_down(&mut self) {
let curr = self
.inner_tree
.get(*self.visit_stack.last().unwrap())
.unwrap();
let count = curr.children().count();
let curr_selection = self.list_state.selected().unwrap();
if self.at_root() {
self.list_state
.select(Some((curr_selection + 1).min(count - 1)));
} else {
// When we are not at the root, we have to account for 1 more "virtual" node, `..`. So
// the count is 1 bigger (select is 0 based, because it's an index)
self.list_state
.select(Some((curr_selection + 1).min(count)));
}
}
/// Scroll the preview window down
fn scroll_preview_window_down(&mut self) {
@ -314,31 +352,44 @@ impl CustomList {
}
}
/// This method return the currently selected command, or None if no command is selected.
/// This method returns the currently selected command, or None if no command is selected.
/// It was extracted from the 'handle_enter()'
///
/// This could probably be integrated into the 'handle_enter()' method as to avoid code
/// This could probably be integrated into the 'handle_enter()' method to avoid code
/// duplication, but I don't want to make too major changes to the codebase.
fn get_selected_command(&self) -> Option<&'static str> {
let curr = self
.inner_tree
.get(*self.visit_stack.last().unwrap())
.unwrap();
let selected = self.list_state.selected().unwrap();
fn get_selected_command(&self) -> Option<Command> {
let selected_index = self.list_state.selected().unwrap_or(0);
println!("Selected Index: {}", selected_index);
// If we are not at the root and the first item is selected, it's the `..` item
if !self.at_root() && selected == 0 {
return None;
}
if self.filter_query.is_empty() {
// No filter query, use the regular tree navigation
let curr = self
.inner_tree
.get(*self.visit_stack.last().unwrap())
.unwrap();
for (mut idx, node) in curr.children().enumerate() {
if !self.at_root() && selected_index == 0 {
return None;
}
let mut actual_index = selected_index;
if !self.at_root() {
idx += 1;
actual_index -= 1; // Adjust for the ".." item if not at root
}
if idx == selected {
return Some(node.value().command);
for (idx, node) in curr.children().enumerate() {
if idx == actual_index {
return Some(node.value().command.clone());
}
}
} else {
// Filter query is active, use the filtered items
if let Some(filtered_node) = self.filtered_items.get(selected_index) {
println!("Filtered Node Name: {}", filtered_node.name);
return Some(filtered_node.command.clone());
}
}
None
}
@ -346,39 +397,47 @@ impl CustomList {
/// - Run a command, if it is the currently selected item,
/// - Go up a directory
/// - Go down into a directory
///
/// Returns `Some(command)` when command is selected, othervise we returns `None`
fn handle_enter(&mut self) -> Option<&'static str> {
// Get the current node (current directory)
let curr = self
.inner_tree
.get(*self.visit_stack.last().unwrap())
.unwrap();
let selected = self.list_state.selected().unwrap();
fn handle_enter(&mut self) -> Option<Command> {
let selected_index = self.list_state.selected().unwrap_or(0);
// if we are not at the root, and the first element is selected,
// we can be sure it's '..', so we go up the directory
if !self.at_root() && selected == 0 {
self.visit_stack.pop();
self.list_state.select(Some(0));
return None;
}
if self.filter_query.is_empty() {
// No filter query, use the regular tree navigation
let curr = self
.inner_tree
.get(*self.visit_stack.last().unwrap())
.unwrap();
for (mut idx, node) in curr.children().enumerate() {
// at this point, we know that we are not on the .. item, and our indexes of the items never had ..
// item. so to balance it out, in case the selection index contains .., se add 1 to our node index
if !self.at_root() {
idx += 1;
if !self.at_root() && selected_index == 0 {
self.visit_stack.pop();
self.list_state.select(Some(0));
return None;
}
if idx == selected {
if node.has_children() {
self.visit_stack.push(node.id());
self.list_state.select(Some(0));
return None;
} else {
return Some(node.value().command);
let mut actual_index = selected_index;
if !self.at_root() {
actual_index -= 1; // Adjust for the ".." item if not at root
}
for (idx, node) in curr.children().enumerate() {
if idx == actual_index {
if node.has_children() {
self.visit_stack.push(node.id());
self.list_state.select(Some(0));
return None;
} else {
return Some(node.value().command.clone());
}
}
}
} else {
// Filter query is active, use the filtered items
if let Some(filtered_node) = self.filtered_items.get(selected_index) {
return Some(filtered_node.command.clone());
}
}
None
}

View File

@ -1,6 +1,7 @@
mod float;
mod list;
mod running_command;
pub mod state;
mod theme;
use std::{
@ -16,13 +17,20 @@ use crossterm::{
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
ExecutableCommand,
};
use include_dir::include_dir;
use list::CustomList;
use ratatui::{
backend::{Backend, CrosstermBackend},
layout::{Constraint, Direction, Layout},
style::{Color, Style},
text::Span,
widgets::{Block, Borders, Paragraph},
Terminal,
};
use running_command::RunningCommand;
use theme::set_theme;
use state::AppState;
use tempdir::TempDir;
use theme::THEMES;
/// This is a binary :), Chris, change this to update the documentation on -h
#[derive(Debug, Parser)]
@ -34,16 +42,29 @@ struct Args {
fn main() -> std::io::Result<()> {
let args = Args::parse();
if args.compat {
set_theme(0);
}
let theme = if args.compat {
THEMES[0].clone()
} else {
THEMES[1].clone()
};
let commands_dir = include_dir!("src/commands");
let temp_dir: TempDir = TempDir::new("linutil_scripts").unwrap();
commands_dir
.extract(temp_dir.path())
.expect("Failed to extract the saved directory");
let state = AppState {
theme,
temp_path: temp_dir.path().to_owned(),
};
stdout().execute(EnterAlternateScreen)?;
enable_raw_mode()?;
let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
terminal.clear()?;
run(&mut terminal)?;
run(&mut terminal, &state)?;
// restore terminal
disable_raw_mode()?;
@ -55,17 +76,51 @@ fn main() -> std::io::Result<()> {
Ok(())
}
fn run<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
fn run<B: Backend>(terminal: &mut Terminal<B>, state: &AppState) -> io::Result<()> {
let mut command_opt: Option<RunningCommand> = None;
let mut custom_list = CustomList::new();
let mut search_input = String::new();
let mut in_search_mode = false;
loop {
// Always redraw
terminal
.draw(|frame| {
custom_list.draw(frame, frame.size());
//Split the terminal into 2 vertical chunks
//One for the search bar and one for the command list
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(3), Constraint::Min(1)].as_ref())
.split(frame.size());
//Set the search bar text (If empty use the placeholder)
let display_text = if search_input.is_empty() {
if in_search_mode {
Span::raw("")
} else {
Span::raw("Press / to search")
}
} else {
Span::raw(&search_input)
};
//Create the search bar widget
let mut search_bar = Paragraph::new(display_text)
.block(Block::default().borders(Borders::ALL).title("Search"))
.style(Style::default().fg(Color::DarkGray));
//Change the color if in search mode
if in_search_mode {
search_bar = search_bar.clone().style(Style::default().fg(Color::Blue));
}
//Render the search bar (First chunk of the screen)
frame.render_widget(search_bar, chunks[0]);
//Render the command list (Second chunk of the screen)
custom_list.draw(frame, chunks[1], state);
if let Some(ref mut command) = &mut command_opt {
command.draw(frame);
command.draw(frame, state);
}
})
.unwrap();
@ -90,8 +145,36 @@ fn run<B: Backend>(terminal: &mut Terminal<B>) -> io::Result<()> {
if key.code == KeyCode::Char('q') {
return Ok(());
}
if let Some(cmd) = custom_list.handle_key(key) {
command_opt = Some(RunningCommand::new(cmd));
//Activate search mode if the forward slash key gets pressed
if key.code == KeyCode::Char('/') {
// Enter search mode
in_search_mode = true;
continue;
}
//Insert user input into the search bar
if in_search_mode {
match key.code {
KeyCode::Char(c) => {
search_input.push(c);
custom_list.filter(search_input.clone());
}
KeyCode::Backspace => {
search_input.pop();
custom_list.filter(search_input.clone());
}
KeyCode::Esc => {
search_input = String::new();
custom_list.filter(search_input.clone());
in_search_mode = false
}
KeyCode::Enter => {
in_search_mode = false;
custom_list.reset_selection();
}
_ => {}
}
} else if let Some(cmd) = custom_list.handle_key(key, state) {
command_opt = Some(RunningCommand::new(cmd, state));
}
}
}

View File

@ -11,7 +11,7 @@ use portable_pty::{
};
use ratatui::{
layout::Size,
style::{Color, Style, Stylize},
style::{Style, Stylize},
text::{Line, Span},
widgets::{Block, Borders},
Frame,
@ -21,7 +21,14 @@ use tui_term::{
widget::PseudoTerminal,
};
use crate::{float::floating_window, theme::get_theme};
use crate::{float::floating_window, state::AppState};
#[derive(Clone)]
pub enum Command {
Raw(&'static str),
LocalFile(&'static str),
None, // Directory
}
/// This is a struct for storing everything connected to a running command
// Create a new instance on every new command you want to run
@ -50,14 +57,22 @@ pub struct RunningCommand {
}
impl RunningCommand {
pub fn new(command: &str) -> Self {
pub fn new(command: Command, state: &AppState) -> Self {
let pty_system = NativePtySystem::default();
let mut cmd = CommandBuilder::new("sh");
cmd.arg("-c");
cmd.arg(command);
let cwd = std::env::current_dir().unwrap();
cmd.cwd(cwd);
let mut cmd = CommandBuilder::new("sh");
match command {
Command::Raw(prompt) => {
cmd.arg("-c");
cmd.arg(prompt);
}
Command::LocalFile(file) => {
cmd.arg(file);
}
Command::None => panic!("Command::None was treated as a command"),
}
cmd.cwd(&state.temp_path);
let pair = pty_system
.openpty(PtySize {
@ -153,59 +168,57 @@ impl RunningCommand {
}
}
pub fn draw(&mut self, frame: &mut Frame) {
{
let theme = get_theme();
// Funny name
let floater = floating_window(frame.size());
pub fn draw(&mut self, frame: &mut Frame, state: &AppState) {
// Funny name
let floater = floating_window(frame.size());
let inner_size = Size {
width: floater.width - 2, // Because we add a `Block` with a border
height: floater.height - 2,
};
let inner_size = Size {
width: floater.width - 2, // Because we add a `Block` with a border
height: floater.height - 2,
};
// When the command is running
let term_border = if !self.is_finished() {
Block::default()
.borders(Borders::ALL)
.title_top(Line::from("Running the command....").centered())
.title_style(Style::default().reversed())
.title_bottom(Line::from("Press Ctrl-C to KILL the command"))
} else {
// This portion is just for pretty colors.
// You can use multiple `Span`s with different styles each, to construct a line,
// which can be used as a list item, or in this case a `Block` title
// When the command is running
let term_border = if !self.is_finished() {
Block::default()
.borders(Borders::ALL)
.title_top(Line::from("Running the command....").centered())
.title_style(Style::default().reversed())
.title_bottom(Line::from("Press Ctrl-C to KILL the command"))
} else {
// This portion is just for pretty colors.
// You can use multiple `Span`s with different styles each, to construct a line,
// which can be used as a list item, or in this case a `Block` title
let mut title_line = if self.get_exit_status().success() {
Line::from(
Span::default()
.content("SUCCESS!")
.style(Style::default().fg(theme.success_color).reversed()),
)
} else {
Line::from(
Span::default()
.content("FAILED!")
.style(Style::default().fg(theme.fail_color).reversed()),
)
};
title_line.push_span(
let mut title_line = if self.get_exit_status().success() {
Line::from(
Span::default()
.content(" press <ENTER> to close this window ")
.style(Style::default()),
);
Block::default()
.borders(Borders::ALL)
.title_top(title_line.centered())
.content("SUCCESS!")
.style(Style::default().fg(state.theme.success_color).reversed()),
)
} else {
Line::from(
Span::default()
.content("FAILED!")
.style(Style::default().fg(state.theme.fail_color).reversed()),
)
};
let screen = self.screen(inner_size); // when the terminal is changing a lot, there
// will be 1 frame of lag on resizing
let pseudo_term = PseudoTerminal::new(&screen).block(term_border);
frame.render_widget(pseudo_term, floater);
}
title_line.push_span(
Span::default()
.content(" press <ENTER> to close this window ")
.style(Style::default()),
);
Block::default()
.borders(Borders::ALL)
.title_top(title_line.centered())
};
let screen = self.screen(inner_size); // when the terminal is changing a lot, there
// will be 1 frame of lag on resizing
let pseudo_term = PseudoTerminal::new(&screen).block(term_border);
frame.render_widget(pseudo_term, floater);
}
/// Send SIGHUB signal, *not* SIGKILL or SIGTERM, to the child process
pub fn kill_child(&mut self) {
if !self.is_finished() {

9
src/state.rs Normal file
View File

@ -0,0 +1,9 @@
use crate::theme::Theme;
use std::path::PathBuf;
pub struct AppState {
/// Selected theme
pub theme: Theme,
/// Path to the root of the unpacked files in /tmp
pub temp_path: PathBuf,
}

View File

@ -1,7 +1,6 @@
use ratatui::style::Color;
pub static mut THEME_IDX: usize = 1;
#[derive(Clone)]
pub struct Theme {
pub dir_color: Color,
pub cmd_color: Color,
@ -29,11 +28,3 @@ pub const THEMES: [Theme; 2] = [
success_color: Color::Rgb(5, 255, 55),
},
];
pub fn get_theme() -> &'static Theme {
&THEMES[unsafe { THEME_IDX }]
}
pub fn set_theme(idx: usize) {
unsafe { THEME_IDX = idx };
}

View File

@ -3,8 +3,6 @@
rc='\033[0m'
red='\033[0;31m'
binary_url="https://github.com/ChrisTitusTech/linutil/releases/latest/download/linutil"
check() {
exit_code=$1
message=$2
@ -18,10 +16,26 @@ check() {
unset message
}
findArch() {
case "$(uname -m)" in
x86_64|amd64) arch="x86_64" ;;
aarch64|arm64) arch="aarch64" ;;
*) check 1 "Unsupported architecture"
esac
}
getUrl() {
case "${arch}" in
x86_64) echo "https://github.com/ChrisTitusTech/linutil/releases/latest/download/linutil";;
*) echo "https://github.com/ChrisTitusTech/linutil/releases/latest/download/linutil-${arch}";;
esac
}
findArch
temp_file=$(mktemp)
check $? "Creating the temporary file"
curl -fsL "$binary_url" -o "$temp_file"
curl -fsL "$(getUrl)" -o "$temp_file"
check $? "Downloading linutil"
chmod +x "$temp_file"

View File

@ -24,6 +24,7 @@ redirect_to_latest_pre_release() {
echo "Using latest Full Release"
url="https://github.com/ChrisTitusTech/linutil/releases/latest/download/linutil"
fi
addArch
echo "Using URL: $url" # Log the URL being used
}
@ -37,6 +38,22 @@ check() {
fi
}
addArch() {
case "${arch}" in
x86_64);;
*) url="${url}-${arch}";;
esac
}
findArch() {
case "$(uname -m)" in
x86_64|amd64) arch="x86_64" ;;
aarch64|arm64) arch="aarch64" ;;
*) check 1 "Unsupported architecture"
esac
}
findArch
redirect_to_latest_pre_release
TMPFILE=$(mktemp)