feat: remove deprecated shell scripts and add Python alternatives for all of them
Some checks failed
Nix Format Check / check-format (push) Failing after 37s

This commit is contained in:
2025-03-10 15:48:33 +01:00
parent a1e145871b
commit 62954eb986
17 changed files with 652 additions and 641 deletions

78
bin/actions/auto-start.py Executable file
View File

@@ -0,0 +1,78 @@
#!/usr/bin/env python3
import os
import sys
import time
import subprocess
# Import helper functions
sys.path.append(os.path.join(os.path.expanduser("~/.dotfiles"), "bin"))
from helpers.functions import printfe, run_command
def check_command_exists(command):
"""Check if a command is available in the system"""
try:
subprocess.run(["which", command],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
return True
except subprocess.CalledProcessError:
return False
def list_screen_sessions():
"""List all screen sessions"""
success, output = run_command(["screen", "-ls"])
return output
def wipe_dead_sessions():
"""Check and clean up dead screen sessions"""
screen_list = list_screen_sessions()
if "Dead" in screen_list:
print("Found dead sessions, cleaning up...")
run_command(["screen", "-wipe"])
def is_app_running(app_name):
"""Check if an app is already running in a screen session"""
screen_list = list_screen_sessions()
return app_name in screen_list
def start_app(app_name, command):
"""Start an application in a screen session"""
printfe("green", f"Starting {app_name} with command: {command}...")
run_command(["screen", "-dmS", app_name] + command.split())
time.sleep(1) # Give it a moment to start
def main():
# Define dictionary with app_name => command mapping
apps = {
"vesktop": "vesktop",
"ktailctl": "flatpak run org.fkoehler.KTailctl",
"ulauncher": "ulauncher --no-window-shadow --hide-window"
}
# Clean up dead sessions if any
wipe_dead_sessions()
print("Starting auto-start applications...")
for app_name, command in apps.items():
# Get the binary name (first part of the command)
command_binary = command.split()[0]
# Check if the command exists
if check_command_exists(command_binary):
# Check if the app is already running
if is_app_running(app_name):
printfe("yellow", f"{app_name} is already running. Skipping...")
continue
# Start the application
start_app(app_name, command)
# Display screen sessions
print(list_screen_sessions())
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,33 +0,0 @@
#!/usr/bin/env bash
source $DOTFILES_PATH/bin/helpers/functions.sh
# Define associative array with app_name => command mapping
declare -A apps=(
["vesktop"]="vesktop"
["ktailctl"]="flatpak run org.fkoehler.KTailctl"
["ulauncher"]="ulauncher --no-window-shadow --hide-window"
)
# check if screen has any dead sessions
if screen -list | grep -q "Dead"; then
screen -wipe
fi
echo "Starting auto-start applications..."
for app_name in "${!apps[@]}"; do
command="${apps[$app_name]}"
command_binary=$(echo $command | awk '{print $1}')
if [ -x "$(command -v $command_binary)" ]; then
if screen -list | grep -q $app_name; then
printfe "%s\n" "yellow" "$app_name is already running. Skipping..."
continue
fi
printfe "%s\n" "green" "Starting $app_name with command: $command..."
screen -dmS $app_name $command
sleep 1
fi
done
screen -ls

30
bin/actions/hello.py Executable file
View File

@@ -0,0 +1,30 @@
#!/usr/bin/env python3
import os
import sys
# Import helper functions
sys.path.append(os.path.join(os.path.expanduser("~/.dotfiles"), "bin"))
from helpers.functions import printfe, logo, _rainbow_color
def welcome():
"""Display welcome message with hostname and username"""
print()
# Get hostname and username
hostname = os.uname().nodename
username = os.environ.get("USER", os.environ.get("USERNAME", "user"))
print("\033[36mYou're logged in on [", end="")
print(_rainbow_color(hostname), end="")
print("\033[36m] as [", end="")
print(_rainbow_color(username), end="")
print("\033[36m]\033[0m")
def main():
logo(continue_after=True)
welcome()
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,20 +0,0 @@
#!/usr/bin/env bash
source $HOMEsource $DOTFILES_PATH/bin/helpers/functions.sh
welcome() {
echo
tput setaf 6
printf "You're logged in on ["
printf $HOSTNAME | lolcat
tput setaf 6
printf "] as "
printf "["
printf $USER | lolcat
tput setaf 6
printf "]\n"
tput sgr0
}
logo continue
welcome

28
bin/actions/help.py Executable file
View File

@@ -0,0 +1,28 @@
#!/usr/bin/env python3
import os
import sys
# Import helper functions
sys.path.append(os.path.join(os.path.expanduser("~/.dotfiles"), "bin"))
from helpers.functions import printfe, println, logo
def main():
# Print logo
logo(continue_after=True)
# Print help
dotfiles_path = os.environ.get("DOTFILES_PATH", os.path.expanduser("~/.dotfiles"))
try:
with open(f"{dotfiles_path}/bin/resources/help.txt", "r") as f:
help_text = f.read()
print(help_text)
except Exception as e:
printfe("red", f"Error reading help file: {e}")
return 1
println(" ", "cyan")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,10 +0,0 @@
#!/usr/bin/env bash
source $DOTFILES_PATH/bin/helpers/functions.sh
# Print logo
logo
# Print help
cat $DOTFILES_PATH/bin/resources/help.txt
println " " "cyan"

168
bin/actions/secrets.py Executable file
View File

@@ -0,0 +1,168 @@
#!/usr/bin/env python3
import os
import sys
import subprocess
import hashlib
import glob
# Import helper functions
sys.path.append(os.path.join(os.path.expanduser("~/.dotfiles"), "bin"))
from helpers.functions import printfe, run_command
def is_wsl():
"""Check if running under WSL"""
try:
with open('/proc/version', 'r') as f:
return 'microsoft' in f.read().lower()
except:
return False
def get_password():
"""Get password from 1Password"""
# Choose the appropriate op command based on WSL status
op_cmd = "op.exe" if is_wsl() else "op"
# Try to get the password
success, output = run_command([op_cmd, "item", "get", "Dotfiles Secrets", "--fields", "password"])
if not success:
printfe("red", "Failed to fetch password from 1Password.")
return None
# Check if we need to use a token
if "use 'op item get" in output:
# Extract the token
token = output.split("use 'op item get ")[1].split(" --")[0]
printfe("cyan", f"Got fetch token: {token}")
# Use the token to get the actual password
success, password = run_command(
[op_cmd, "item", "get", token, "--reveal", "--fields", "password"]
)
if not success:
return None
return password
else:
# We already got the password
return output
def prompt_for_password():
"""Ask for password manually"""
import getpass
printfe("cyan", "Enter the password manually: ")
password = getpass.getpass("")
if not password:
printfe("red", "Password cannot be empty.")
sys.exit(1)
printfe("green", "Password entered successfully.")
return password
def calculate_checksum(file_path):
"""Calculate SHA256 checksum of a file"""
sha256_hash = hashlib.sha256()
with open(file_path, "rb") as f:
for byte_block in iter(lambda: f.read(4096), b""):
sha256_hash.update(byte_block)
return sha256_hash.hexdigest()
def encrypt_folder(folder_path, password):
"""Recursively encrypt files in a folder"""
for item in glob.glob(os.path.join(folder_path, "*")):
# Skip .gpg and .sha256 files
if item.endswith(".gpg") or item.endswith(".sha256"):
continue
# Handle directories recursively
if os.path.isdir(item):
encrypt_folder(item, password)
continue
# Calculate current checksum
current_checksum = calculate_checksum(item)
checksum_file = f"{item}.sha256"
# Check if file changed since last encryption
if os.path.exists(checksum_file):
with open(checksum_file, 'r') as f:
previous_checksum = f.read().strip()
if current_checksum == previous_checksum:
continue
# Remove existing .gpg file if it exists
gpg_file = f"{item}.gpg"
if os.path.exists(gpg_file):
os.remove(gpg_file)
# Encrypt the file
printfe("cyan", f"Encrypting {item}...")
cmd = [
"gpg", "--quiet", "--batch", "--yes", "--symmetric",
"--cipher-algo", "AES256", "--armor",
"--passphrase", password,
"--output", gpg_file, item
]
success, _ = run_command(cmd)
if success:
printfe("cyan", f"Staging {item} for commit...")
run_command(["git", "add", "-f", gpg_file])
# Update checksum file
with open(checksum_file, 'w') as f:
f.write(current_checksum)
else:
printfe("red", f"Failed to encrypt {item}")
def decrypt_folder(folder_path, password):
"""Recursively decrypt files in a folder"""
for item in glob.glob(os.path.join(folder_path, "*")):
# Handle .gpg files
if item.endswith(".gpg"):
output_file = item[:-4] # Remove .gpg extension
printfe("cyan", f"Decrypting {item}...")
cmd = [
"gpg", "--quiet", "--batch", "--yes", "--decrypt",
"--passphrase", password,
"--output", output_file, item
]
success, _ = run_command(cmd)
if not success:
printfe("red", f"Failed to decrypt {item}")
# Process directories recursively
elif os.path.isdir(item):
printfe("cyan", f"Decrypting folder {item}...")
decrypt_folder(item, password)
def main():
if len(sys.argv) != 2 or sys.argv[1] not in ["encrypt", "decrypt"]:
printfe("red", "Usage: secrets.py [encrypt|decrypt]")
return 1
# Get the dotfiles path
dotfiles_path = os.environ.get("DOTFILES_PATH", os.path.expanduser("~/.dotfiles"))
secrets_path = os.path.join(dotfiles_path, "secrets")
# Get the password
password = get_password()
if not password:
password = prompt_for_password()
# Perform the requested action
if sys.argv[1] == "encrypt":
printfe("cyan", "Encrypting secrets...")
encrypt_folder(secrets_path, password)
else: # decrypt
printfe("cyan", "Decrypting secrets...")
decrypt_folder(secrets_path, password)
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,118 +0,0 @@
#!/usr/bin/env bash
source $DOTFILES_PATH/bin/helpers/functions.sh
if is_wsl; then
output=$(op.exe item get "Dotfiles Secrets" --fields password)
else
output=$(op item get "Dotfiles Secrets" --fields password)
fi
# Check if command was a success
if [[ $? -ne 0 ]]; then
printfe "%s\n" "red" "Failed to fetch password from 1Password."
fi
# In case the output does not contain use 'op item get, it means the password was fetched successfully
# Without having to reveal the password using an external command
if [[ ! $output == *"use 'op item get"* ]]; then
password=$output
else
token=$(echo "$output" | grep -oP "(?<=\[use 'op item get ).*(?= --)")
printfe "%s\n" "cyan" "Got fetch token: $token"
if is_wsl; then
password=$(op.exe item get $token --reveal --field password)
else
password=$(op item get $token --reveal --fields password)
fi
fi
# only continue if password isn't empty
if [[ -z "$password" ]]; then
printfe "%s\n" "red" "Something went wrong while fetching the password from 1Password."
# Ask for manual input
printfe "%s" "cyan" "Enter the password manually: "
read -s password
echo
if [[ -z "$password" ]]; then
printfe "%s\n" "red" "Password cannot be empty."
exit 1
fi
printfe "%s\n" "green" "Password entered successfully."
fi
encrypt_folder() {
for file in $1/*; do
# Skip if the current file is a .gpg file
if [[ $file == *.gpg ]]; then
continue
fi
# Skip if the current file is a .sha256 file
if [[ $file == *.sha256 ]]; then
continue
fi
# If the file is a directory, call this function recursively
if [[ -d $file ]]; then
encrypt_folder $file
continue
fi
current_checksum=$(sha256sum "$file" | awk '{ print $1 }')
checksum_file="$file.sha256"
if [[ -f $checksum_file ]]; then
previous_checksum=$(cat $checksum_file)
if [[ $current_checksum == $previous_checksum ]]; then
continue
fi
fi
# If the file has an accompanying .gpg file, remove it
if [[ -f $file.gpg ]]; then
rm "$file.gpg"
fi
printfe "%s\n" "cyan" "Encrypting $file..."
gpg --quiet --batch --yes --symmetric --cipher-algo AES256 --armor --passphrase="$password" --output "$file.gpg" "$file"
printfe "%s\n" "cyan" "Staging $file for commit..."
git add -f "$file.gpg"
# Update checksum file
echo $current_checksum > "$checksum_file"
done
}
# Recursively decrypt all .gpg files under the folder specified, recursively call this function for sub folders!
# Keep the original file name minus the .gpg extension
decrypt_folder() {
for file in $1/*; do
# Skip if current file is a .gpg file
if [[ $file == *.gpg ]]; then
filename=$(basename $file .gpg)
printfe "%s\n" "cyan" "Decrypting $file..."
gpg --quiet --batch --yes --decrypt --passphrase="$password" --output $1/$filename $file
fi
# If file is actually a folder, call this function recursively
if [[ -d $file ]]; then
printfe "%s\n" "cyan" "Decrypting folder $file..."
decrypt_folder $file
fi
done
}
if [[ "$1" == "decrypt" ]]; then
printfe "%s\n" "cyan" "Decrypting secrets..."
decrypt_folder $DOTFILES_PATH/secrets
elif [[ "$1" == "encrypt" ]]; then
printfe "%s\n" "cyan" "Encrypting secrets..."
encrypt_folder $DOTFILES_PATH/secrets
fi

163
bin/actions/update.py Executable file
View File

@@ -0,0 +1,163 @@
#!/usr/bin/env python3
import os
import sys
import subprocess
import argparse
# Import helper functions
sys.path.append(os.path.join(os.path.expanduser("~/.dotfiles"), "bin"))
from helpers.functions import printfe, run_command
def help_message():
"""Print help message and exit"""
printfe("green", "Usage: upgrade.py [options]")
printfe("green", "Options:")
printfe("green", " --ha, -H Upgrade Home Manager packages.")
printfe("green", " --ansible, -A Upgrade Ansible packages.")
printfe("green", " --ansible-verbose Upgrade Ansible packages with verbose output. (-vvv)")
printfe("green", " --full-speed, -F Upgrade packages and use all available cores for compilation. (Default: 8 cores)")
printfe("green", " --help, -h Display this help message.")
return 0
def ensure_ansible_collections():
"""Ensure required Ansible collections are installed"""
# List of required collections that can be expanded in the future
required_collections = [
"community.general",
]
printfe("cyan", "Checking for required Ansible collections...")
status, output = run_command(["ansible-galaxy", "collection", "list"], shell=False)
if not status:
printfe("red", "Failed to list Ansible collections")
return False
# Check each required collection and install if missing
for collection in required_collections:
if collection not in output:
printfe("yellow", f"Installing {collection} collection...")
status, install_output = run_command(["ansible-galaxy", "collection", "install", collection], shell=False)
if not status:
printfe("yellow", f"Warning: Failed to install {collection} collection: {install_output}")
printfe("yellow", f"Continuing anyway, but playbook might fail if it requires {collection}")
else:
printfe("green", f"Successfully installed {collection} collection")
return True
def main():
# Parse arguments
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument("--ha", "-H", action="store_true", help="Upgrade Home Manager packages")
parser.add_argument("--ansible", "-A", action="store_true", help="Upgrade Ansible packages")
parser.add_argument("--ansible-verbose", action="store_true", help="Upgrade Ansible packages with verbose output")
parser.add_argument("--full-speed", "-F", action="store_true", help="Use all available cores")
parser.add_argument("--help", "-h", action="store_true", help="Display help message")
args = parser.parse_args()
if args.help:
return help_message()
# If no specific option provided, run both
if not args.ha and not args.ansible and not args.ansible_verbose:
args.ha = True
args.ansible = True
# If ansible_verbose is set, also set ansible
if args.ansible_verbose:
args.ansible = True
# Set cores and jobs based on full-speed flag
if args.full_speed:
import multiprocessing
cores = jobs = multiprocessing.cpu_count()
else:
cores = 8
jobs = 1
printfe("cyan", f"Limiting to {cores} cores with {jobs} jobs.")
# Home Manager update
if args.ha:
dotfiles_path = os.environ.get("DOTFILES_PATH", os.path.expanduser("~/.dotfiles"))
hostname = os.uname().nodename
printfe("cyan", "Updating Home Manager flake...")
os.chdir(f"{dotfiles_path}/config/home-manager")
status, output = run_command(["nix", "--extra-experimental-features", "nix-command",
"--extra-experimental-features", "flakes", "flake", "update"],
shell=False)
if not status:
printfe("red", f"Failed to update Home Manager flake: {output}")
return 1
# Check if home-manager is installed
status, _ = run_command(["which", "home-manager"], shell=False)
if status:
printfe("cyan", "Cleaning old backup files...")
backup_file = os.path.expanduser("~/.config/mimeapps.list.backup")
if os.path.exists(backup_file):
os.remove(backup_file)
printfe("cyan", "Upgrading Home Manager packages...")
env = os.environ.copy()
env["NIXPKGS_ALLOW_UNFREE"] = "1"
cmd = ["home-manager", "--extra-experimental-features", "nix-command",
"--extra-experimental-features", "flakes", "switch", "-b", "backup",
f"--flake", f".#{hostname}", "--impure", "--cores", str(cores), "-j", str(jobs)]
result = subprocess.run(cmd, env=env)
if result.returncode != 0:
printfe("red", "Failed to upgrade Home Manager packages.")
return 1
else:
printfe("red", "Home Manager is not installed.")
return 1
# Ansible update
if args.ansible:
dotfiles_path = os.environ.get("DOTFILES_PATH", os.path.expanduser("~/.dotfiles"))
hostname = os.uname().nodename
username = os.environ.get("USER", os.environ.get("USERNAME", "user"))
# Check if ansible is installed
status, _ = run_command(["which", "ansible-playbook"], shell=False)
if not status:
printfe("yellow", "Ansible is not installed, installing it with pipx...")
status, output = run_command(["pipx", "install", "--include-deps", "ansible", "ansible-lint"], shell=False)
if not status:
printfe("red", f"Failed to install Ansible: {output}")
return 1
# Ensure required collections are installed
if not ensure_ansible_collections():
printfe("red", "Failed to ensure required Ansible collections are installed")
return 1
printfe("cyan", "Running Ansible playbook...")
ansible_cmd = [
"ansible-playbook",
"-i", f"{dotfiles_path}/config/ansible/inventory.ini",
f"{dotfiles_path}/config/ansible/main.yml",
"--extra-vars", f"hostname={hostname}",
"--extra-vars", f"ansible_user={username}",
"--limit", hostname,
"--ask-become-pass"
]
if args.ansible_verbose:
ansible_cmd.append("-vvv")
result = subprocess.run(ansible_cmd)
if result.returncode != 0:
printfe("red", "Failed to upgrade Ansible packages.")
return 1
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,85 +0,0 @@
#!/usr/bin/env bash
source $DOTFILES_PATH/bin/helpers/functions.sh
help() {
printfe "%s\n" "green" "Usage: upgrade.sh [options]"
printfe "%s\n" "green" "Options:"
printfe "%s\n" "green" " --ha, -H Upgrade Home Manager packages."
printfe "%s\n" "green" " --ansible, -A Upgrade Ansible packages."
printfe "%s\n" "green" " --ansible-verbose Upgrade Ansible packages with verbose output. (-vvv)"
printfe "%s\n" "green" " --full-speed, -F Upgrade packages and use all available cores for compilation. (Default: 8 cores)"
printfe "%s\n" "green" " --help, -h Display this help message."
exit 0
}
while [[ "$#" -gt 0 ]]; do
case $1 in
--ha|-H) RUN_HA=true ;;
--ansible|-A) RUN_ANSIBLE=true ;;
--ansible-verbose)
RUN_ANSIBLE=true
ANSIBLE_VERBOSE=true ;;
--full-speed|-F) FULL_SPEED=true ;;
--help|-h) help ;;
*) echo "Unknown parameter passed: $1";
help ;;
esac
shift
done
if [[ -z "$RUN_HA" && -z "$RUN_ANSIBLE" ]]; then
RUN_HA=true
RUN_ANSIBLE=true
fi
# Check if --full-speed flag is passed, otherwise use --cores 8 -j 1
if [[ "$FULL_SPEED" == true ]]; then
CORES=$(nproc)
JOBS=$(nproc)
else
CORES=8
JOBS=1
fi
printfe "%s\n" "cyan" "Limiting to $CORES cores with $JOBS jobs."
if [[ "$RUN_HA" == true ]]; then
printfe "%s\n" "cyan" "Updating Home Manager flake..."
cd $DOTFILES_PATH/config/home-manager && nix --extra-experimental-features nix-command --extra-experimental-features flakes flake update
if command -v home-manager &> /dev/null; then
printfe "%s\n" "cyan" "Cleaning old backup files..."
rm -rf $HOME/.config/mimeapps.list.backup
printfe "%s\n" "cyan" "Upgrading Home Manager packages..."
cd $DOTFILES_PATH/config/home-manager && NIXPKGS_ALLOW_UNFREE=1 home-manager --extra-experimental-features nix-command --extra-experimental-features flakes switch -b backup --flake .#$HOSTNAME --impure --cores $CORES -j $JOBS
if [[ $? -ne 0 ]]; then
printfe "%s\n" "red" "Failed to upgrade Home Manager packages."
exit 1
fi
else
printfe "%s\n" "red" "Home Manager is not installed."
exit 1
fi
fi
if [[ "$RUN_ANSIBLE" == true ]]; then
if ! command -v ansible-playbook &> /dev/null; then
printfe "%s\n" "yellow" "Ansible is not installed, installing it with pipx..."
pipx install --include-deps ansible ansible-lint
if [[ $? -ne 0 ]]; then
printfe "%s\n" "red" "Failed to install Ansible."
exit 1
fi
fi
printfe "%s\n" "cyan" "Running Ansible playbook..."
cd $DOTFILES_PATH/config/ansible && ansible-playbook -i $DOTFILES_PATH/config/ansible/inventory.ini $DOTFILES_PATH/config/ansible/main.yml --extra-vars "hostname=$HOSTNAME" --extra-vars "ansible_user=$USER" --limit $HOSTNAME --ask-become-pass ${ANSIBLE_VERBOSE:+-vvv}
if [[ $? -ne 0 ]]; then
printfe "%s\n" "red" "Failed to upgrade Ansible packages."
exit 1
fi
fi