Compare commits

..

5 Commits

Author SHA1 Message Date
renovate[bot]
ec17b125da chore(deps): update image python to v3.14.3
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-06 00:52:46 +00:00
13dd701eb7 feat(a): improve encryption script with better error handling
- Add dependency check for age and curl with install instructions
- Add --delete flag to remove originals after encryption
- Add -f/--force flag to control overwrite behavior
- Skip already-encrypted .age files during encryption
- Include hidden files (dotglob) when encrypting directories
- Handle empty directories gracefully with nullglob
- Allow flags in any position (proper option parsing)
- Add set -euo pipefail for better error handling
- Update documentation with all features and examples
- Bump version to 1.1.0
2026-02-06 01:51:01 +02:00
cfde007494 fix(shell): clean up rcfiles and remove redundancies
- Remove deprecated GREP_OPTIONS (handled via alias)
- Quote $ZSH_COMPDUMP to prevent word splitting
- Remove duplicate vim alias (nvim alias takes precedence)
- Consolidate completion path to ZSH_CUSTOM_COMPLETION_PATH
- Simplify PATH setup in rcfiles, centralize in exports
- Move LM Studio PATH from rcfiles to exports
- Add clarifying comments for macOS-specific ssh-add
2026-02-06 00:09:03 +02:00
ed4aa2ffe1 feat(scripts): add install-dnf-packages.sh for Fedora/RHEL 2026-02-05 23:46:10 +02:00
bcf11406b6 feat(scripts): add install-apt-packages.sh for Debian/Ubuntu
Install essential developer packages via apt:
- Build tools: build-essential, cmake, pkg-config, autoconf, automake, libtool
- Dev libraries: libssl-dev, libffi-dev, zlib1g-dev, libreadline-dev, etc.
- CLI utilities: jq, tmux, tree, unzip, shellcheck, socat, gnupg

Curated to avoid duplicates with cargo/go installs (ripgrep, fd, fzf, etc.).
Uses batched apt install for efficiency, exits gracefully on non-Debian systems.
2026-02-05 23:42:16 +02:00
9 changed files with 435 additions and 90 deletions

View File

@@ -1 +1 @@
3.14.2
3.14.3

View File

@@ -2,6 +2,7 @@
# shellcheck shell=bash
export DOTFILES="$HOME/.dotfiles"
# Minimal PATH for x-have and utilities; full PATH set in shared.sh/exports
export PATH="$HOME/.local/bin:$DOTFILES/local/bin:$PATH"
export SHARED_SCRIPTS_SOURCED=0
@@ -11,7 +12,7 @@ source "$DOTFILES/config/shared.sh"
[ -f "${DOTFILES}/config/fzf/fzf.bash" ] &&
source "${DOTFILES}/config/fzf/fzf.bash"
# Import ssh keys in keychain
# Import ssh keys in keychain (macOS-specific -A flag, silently fails on Linux)
ssh-add -A 2>/dev/null
x-have antidot && {
@@ -22,5 +23,3 @@ PROMPT_DIRTRIM=3
PROMPT_COMMAND='PS1_CMD1=$(git branch --show-current 2>/dev/null)'
PS1='\[\e[95m\]\u\[\e[0m\]@\[\e[38;5;22;2m\]\h\[\e[0m\] \[\e[38;5;33m\]\w\[\e[0m\] \[\e[92;2m\]${PS1_CMD1}\n\[\e[39m\]➜\[\e[0m\] '
# Added by LM Studio CLI (lms)
export PATH="$PATH:$HOME/.lmstudio/bin"

View File

@@ -7,18 +7,13 @@
autoload -U promptinit; promptinit
export DOTFILES="$HOME/.dotfiles"
LOCAL_SHARE="$HOME/.local/share"
export PATH="$HOME/.local/bin:$DOTFILES/local/bin:$LOCAL_SHARE/nvim/mason/bin:$LOCAL_SHARE/bob/nvim-bin:$LOCAL_SHARE/cargo/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"
# Minimal PATH for x-have and utilities; full PATH set in shared.sh/exports
export PATH="$HOME/.local/bin:$DOTFILES/local/bin:$PATH"
export SHARED_SCRIPTS_SOURCED=0
source "$DOTFILES/config/shared.sh"
# zsh completions directory
[ -z "$ZSH_COMPLETIONS" ] && export ZSH_COMPLETIONS="$XDG_CONFIG_HOME/zsh/completion"
# Add zsh completions to FPATH, compinit will be called later
FPATH="$ZSH_COMPLETIONS:$FPATH"
# zsh completions directory (ZSH_CUSTOM_COMPLETION_PATH set in shared.sh)
ZSH_COMPDUMP="$XDG_CACHE_HOME/zsh/zcompdump-${SHORT_HOST}-${ZSH_VERSION}"
source "$DOTFILES/config/zsh/antidote.zsh"
@@ -37,12 +32,10 @@ source_fzf_config
x-have antidot && eval "$(antidot init)"
autoload -Uz compinit bashcompinit
compinit -d $ZSH_COMPDUMP
compinit -d "$ZSH_COMPDUMP"
bashcompinit
# To customize prompt, run `p10k configure` or edit ~/.p10k.zsh.
export P10K_CONFIG="$DOTFILES/config/zsh/p10k.zsh"
[[ ! -f "$P10K_CONFIG" ]] || source "$P10K_CONFIG"
# Added by LM Studio CLI (lms)
export PATH="$PATH:$HOME/.lmstudio/bin"

View File

@@ -7,8 +7,6 @@ x-have eza && {
alias ls="eza -h -s=type --git --icons --group-directories-first"
}
alias vim='vim -u "$XDG_CONFIG_HOME/vim/vimrc"'
# Easier navigation: .., ..., ....
alias ..="cd .."
alias ...="cd ../.."

View File

@@ -282,7 +282,8 @@ export LESSHISTFILE="$XDG_STATE_HOME"/less/history
export MANPAGER="less -X"
# Always enable colored `grep` output
export GREP_OPTIONS="--color=auto"
# Note: GREP_OPTIONS is deprecated since GNU grep 2.21
# Color is handled via alias in config/alias
# check the window size after each command and, if necessary,
# update the values of LINES and COLUMNS.
@@ -436,6 +437,10 @@ msg "Setting up Wakatime configuration"
export WAKATIME_HOME="$XDG_STATE_HOME/wakatime"
x-dc "$WAKATIME_HOME"
# LM Studio CLI
msg "Setting up LM Studio configuration"
export PATH="$PATH:$HOME/.lmstudio/bin"
# Misc
msg "Setting up miscellaneous configuration"
export ZSHZ_DATA="$XDG_STATE_HOME/z"

View File

@@ -1,7 +1,9 @@
#!/usr/bin/env bash
# A script for encrypting and decrypting files or directories with age and SSH keys
VERSION="1.0.0"
set -euo pipefail
VERSION="1.1.0"
# Default ENV values
KEYS_FILE="${AGE_KEYSFILE:-$HOME/.ssh/keys.txt}"
@@ -9,14 +11,49 @@ KEYS_SOURCE="${AGE_KEYSSOURCE:-https://github.com/ivuorinen.keys}"
LOG_FILE="${AGE_LOGFILE:-$HOME/.cache/a.log}"
VERBOSE=false
DELETE_ORIGINAL=false
FORCE=false
# Parse flags for verbosity
for arg in "$@"; do
if [[ "$arg" == "-v" || "$arg" == "--verbose" ]]; then
VERBOSE=true
break
# Check for required dependencies
check_dependencies()
{
if ! command -v age &> /dev/null; then
echo "Error: 'age' is not installed. Please install it first." >&2
echo " brew install age # macOS" >&2
echo " apt install age # Debian/Ubuntu" >&2
echo " dnf install age # Fedora" >&2
exit 1
fi
done
if ! command -v curl &> /dev/null; then
echo "Error: 'curl' is not installed." >&2
exit 1
fi
}
# Parse flags
parse_flags()
{
local args=()
for arg in "$@"; do
case "$arg" in
-v | --verbose)
VERBOSE=true
;;
--delete)
DELETE_ORIGINAL=true
;;
-f | --force)
FORCE=true
;;
*)
args+=("$arg")
;;
esac
done
# Return remaining arguments
printf '%s\n' "${args[@]}"
}
# Ensure log directory and file exist with correct permissions
prepare_log_file()
@@ -38,8 +75,6 @@ prepare_log_file()
chmod 0600 "$LOG_FILE"
}
prepare_log_file
# Logging function
log_message()
{
@@ -56,7 +91,7 @@ log_message()
print_help()
{
cat << EOF
Usage: a [command] [file_or_directory] [options]
Usage: a [options] [command] [file_or_directory]
Commands:
e, enc, encrypt Encrypt the specified file or directory
@@ -65,12 +100,14 @@ Commands:
version, --version Show version information
Options:
-v, --verbose Print log messages to console in addition to writing to log file
-v, --verbose Print log messages to console
--delete Delete original files after successful encryption
-f, --force Overwrite existing output files without prompting
Environment Variables:
AGE_KEYSFILE Path to the SSH keys file (default: $HOME/.ssh/keys.txt)
AGE_KEYSFILE Path to the SSH keys file (default: \$HOME/.ssh/keys.txt)
AGE_KEYSSOURCE URL to fetch SSH keys if keys file does not exist
AGE_LOGFILE Path to the log file (default: $HOME/.cache/a.log)
AGE_LOGFILE Path to the log file (default: \$HOME/.cache/a.log)
Examples:
Encrypt a file:
@@ -79,14 +116,21 @@ Examples:
Encrypt a directory:
a e /path/to/directory
Encrypt and delete originals:
a --delete e file.txt
Decrypt a file:
a d file.txt.age
Force overwrite existing files:
a -f e file.txt
Specify a custom keys file:
AGE_KEYSFILE=/path/to/keys.txt a e file.txt
Specify a custom keys source and log file:
AGE_KEYSSOURCE=https://example.com/keys.txt AGE_LOGFILE=/tmp/a.log a d file.txt.age
Requirements:
- age (encryption tool): https://github.com/FiloSottile/age
- curl (for fetching keys)
EOF
}
@@ -115,26 +159,104 @@ fetch_keys_if_missing()
fi
}
# Function to encrypt a single file
encrypt_single_file()
{
local file="$1"
# Skip already encrypted files
if [[ "$file" == *.age ]]; then
log_message "Skipping already encrypted file: $file"
return 0
fi
local output_file="${file}.age"
# Check if output file exists
if [[ -f "$output_file" && "$FORCE" != true ]]; then
log_message "Error: Output file '$output_file' already exists. Use --force to overwrite."
return 1
fi
fetch_keys_if_missing
local temp_file
temp_file="$(mktemp -p "$(dirname "$file")")"
if age -R "$KEYS_FILE" "$file" > "$temp_file" && mv "$temp_file" "$output_file"; then
log_message "File encrypted successfully: $output_file"
if [[ "$DELETE_ORIGINAL" == true ]]; then
rm -f "$file"
log_message "Original file deleted: $file"
fi
else
rm -f "$temp_file"
log_message "Error: Failed to encrypt file '$file'."
return 1
fi
}
# Function to encrypt files or directories
encrypt_file_or_directory()
{
local file="$1"
if [[ -d "$file" ]]; then
for f in "$file"/*; do
# Enable dotglob to include hidden files
shopt -s dotglob nullglob
local files=("$file"/*)
shopt -u dotglob nullglob
if [[ ${#files[@]} -eq 0 ]]; then
log_message "Warning: Directory '$file' is empty."
return 0
fi
for f in "${files[@]}"; do
encrypt_file_or_directory "$f"
done
elif [[ -f "$file" ]]; then
fetch_keys_if_missing
local output_file="${file}.age"
local temp_file
temp_file="$(mktemp -p "$(dirname "$file")")"
if age -R "$KEYS_FILE" "$file" > "$temp_file" && mv "$temp_file" "$output_file"; then
log_message "File encrypted successfully: $output_file"
else
rm -f "$temp_file"
log_message "Error: Failed to encrypt file '$file'."
exit 1
encrypt_single_file "$file"
else
log_message "Warning: '$file' is not a file or directory, skipping."
fi
}
# Function to decrypt a single file
decrypt_single_file()
{
local file="$1"
if [[ ! "$file" == *.age ]]; then
log_message "Skipping non-.age file: $file"
return 0
fi
local output_file="${file%.age}"
# Check if output file exists
if [[ -f "$output_file" && "$FORCE" != true ]]; then
log_message "Error: Output file '$output_file' already exists. Use --force to overwrite."
return 1
fi
fetch_keys_if_missing
local temp_file
temp_file="$(mktemp -p "$(dirname "$file")")"
if age -d -i "$KEYS_FILE" "$file" > "$temp_file" && mv "$temp_file" "$output_file"; then
log_message "File decrypted successfully: $output_file"
if [[ "$DELETE_ORIGINAL" == true ]]; then
rm -f "$file"
log_message "Encrypted file deleted: $file"
fi
else
rm -f "$temp_file"
log_message "Error: Failed to decrypt file '$file'."
return 1
fi
}
@@ -142,54 +264,76 @@ encrypt_file_or_directory()
decrypt_file_or_directory()
{
local file="$1"
if [[ -d "$file" ]]; then
for f in "$file"/*.age; do
decrypt_file_or_directory "$f"
# Enable nullglob to handle no matches gracefully
shopt -s nullglob
local files=("$file"/*.age)
shopt -u nullglob
if [[ ${#files[@]} -eq 0 ]]; then
log_message "Warning: No .age files found in directory '$file'."
return 0
fi
for f in "${files[@]}"; do
decrypt_single_file "$f"
done
elif [[ -f "$file" ]]; then
fetch_keys_if_missing
local output_file="${file%.age}"
local temp_file
temp_file="$(mktemp -p "$(dirname "$file")")"
if age -d -i "$KEYS_FILE" "$file" > "$temp_file" && mv "$temp_file" "$output_file"; then
log_message "File decrypted successfully: $output_file"
else
rm -f "$temp_file"
log_message "Error: Failed to decrypt file '$file'."
exit 1
fi
decrypt_single_file "$file"
else
log_message "Warning: '$file' is not a file or directory, skipping."
fi
}
# Main logic
case "$1" in
e | enc | encrypt)
if [[ $# -lt 2 ]]; then
log_message "Error: No file or directory specified for encryption."
# Main entry point
main()
{
check_dependencies
# Parse flags and get remaining arguments
mapfile -t ARGS < <(parse_flags "$@")
prepare_log_file
local command="${ARGS[0]:-}"
local target="${ARGS[1]:-}"
case "$command" in
e | enc | encrypt)
if [[ -z "$target" ]]; then
log_message "Error: No file or directory specified for encryption."
print_help
exit 1
fi
encrypt_file_or_directory "$target"
;;
d | dec | decrypt)
if [[ -z "$target" ]]; then
log_message "Error: No file or directory specified for decryption."
print_help
exit 1
fi
decrypt_file_or_directory "$target"
;;
help | --help | -h)
print_help
;;
version | --version)
print_version
;;
"")
print_help
exit 1
fi
encrypt_file_or_directory "$2"
;;
d | dec | decrypt)
if [[ $# -lt 2 ]]; then
log_message "Error: No file or directory specified for decryption."
;;
*)
log_message "Error: Unknown command '$command'"
print_help
exit 1
fi
decrypt_file_or_directory "$2"
;;
help | --help)
print_help
;;
version | --version)
print_version
;;
*)
log_message "Error: Unknown command '$1'"
print_help
exit 1
;;
esac
;;
esac
}
main "$@"
# vim: ft=bash:syn=sh:ts=2:sw=2:et:ai:nowrap

View File

@@ -2,28 +2,76 @@
Encrypt or decrypt files and directories using `age` and your GitHub SSH keys.
## Requirements
- [age](https://github.com/FiloSottile/age) - encryption tool
- curl - for fetching SSH keys
Install age:
```bash
brew install age # macOS
apt install age # Debian/Ubuntu
dnf install age # Fedora
```
## Usage
```bash
a encrypt <file|dir>
a decrypt <file.age|dir>
a [options] <command> <file|directory>
```
Commands:
- `e`, `enc`, `encrypt` - encrypt files
- `d`, `dec`, `decrypt` - decrypt files
- `help`, `--help`, `-h` - show help
- `version`, `--version` - show version
Options:
- `-v`, `--verbose` show log output
- `-v`, `--verbose` - show log output
- `--delete` - delete original files after successful operation
- `-f`, `--force` - overwrite existing output files
Environment variables:
- `AGE_KEYSFILE` location of the keys file
- `AGE_KEYSSOURCE` URL to fetch keys if missing
- `AGE_LOGFILE` log file path
- `AGE_KEYSFILE` - location of the keys file (default: `~/.ssh/keys.txt`)
- `AGE_KEYSSOURCE` - URL to fetch keys if missing (default: GitHub keys)
- `AGE_LOGFILE` - log file path (default: `~/.cache/a.log`)
## Example
## Examples
```bash
# Encrypt a file
a encrypt secret.txt
# Encrypt with short command
a e secret.txt
# Decrypt a file
a decrypt secret.txt.age
a d secret.txt.age
# Encrypt a directory (includes hidden files)
a e /path/to/secrets/
# Encrypt and delete originals
a --delete e secret.txt
# Force overwrite existing .age file
a -f e secret.txt
# Verbose output
a -v e secret.txt
```
## Behavior
- Encrypting a directory processes all files recursively, including hidden files
- Already encrypted files (`.age`) are skipped during encryption
- Only `.age` files are processed during directory decryption
- Original files are preserved by default (use `--delete` to remove them)
- Output files are not overwritten by default (use `--force` to overwrite)
<!-- vim: set ft=markdown spell spelllang=en_us cc=80 : -->

75
scripts/install-apt-packages.sh Executable file
View File

@@ -0,0 +1,75 @@
#!/usr/bin/env bash
set -euo pipefail
# @description Install essential apt packages for development.
#
# shellcheck source=shared.sh
source "$DOTFILES/config/shared.sh"
msgr run "Starting to install apt packages"
if ! command -v apt &> /dev/null; then
msgr warn "apt not found (not a Debian-based system)"
exit 0
fi
packages=(
# Build essentials
build-essential # gcc, g++, make
cmake # Cross-platform build system
pkg-config # Helper for compiling against libraries
autoconf # Automatic configure script builder
automake # Makefile generator
libtool # Generic library support script
# Libraries for compiling languages
libssl-dev # SSL development headers
libffi-dev # Foreign function interface
zlib1g-dev # Compression library
libreadline-dev # Command-line editing
libbz2-dev # Bzip2 compression
libsqlite3-dev # SQLite database
libncurses-dev # Terminal UI library
# CLI utilities (not in cargo/go/npm)
jq # JSON processor
tmux # Terminal multiplexer
tree # Directory listing
unzip # Archive extraction
shellcheck # Shell script linter
socat # Multipurpose network relay
gnupg # GPG encryption/signing
software-properties-common # add-apt-repository command
)
install_packages()
{
local to_install=()
for pkg in "${packages[@]}"; do
pkg="${pkg%%#*}"
pkg="${pkg// /}"
[[ -z "$pkg" ]] && continue
if dpkg -s "$pkg" &> /dev/null; then
msgr ok "$pkg already installed"
else
to_install+=("$pkg")
fi
done
if [[ ${#to_install[@]} -gt 0 ]]; then
msgr run "Installing ${#to_install[@]} packages: ${to_install[*]}"
sudo apt update
sudo apt install -y "${to_install[@]}"
else
msgr ok "All packages already installed"
fi
}
main()
{
install_packages
msgr yay "apt package installations complete"
}
main "$@"

83
scripts/install-dnf-packages.sh Executable file
View File

@@ -0,0 +1,83 @@
#!/usr/bin/env bash
set -euo pipefail
# @description Install essential dnf packages for development.
#
# shellcheck source=shared.sh
source "$DOTFILES/config/shared.sh"
msgr run "Starting to install dnf packages"
if ! command -v dnf &> /dev/null; then
msgr warn "dnf not found (not a Fedora/RHEL-based system)"
exit 0
fi
packages=(
# Build essentials (individual packages, group handled separately)
cmake # Cross-platform build system
pkgconfig # Helper for compiling against libraries
autoconf # Automatic configure script builder
automake # Makefile generator
libtool # Generic library support script
# Libraries for compiling languages
openssl-devel # SSL development headers
libffi-devel # Foreign function interface
zlib-devel # Compression library
readline-devel # Command-line editing
bzip2-devel # Bzip2 compression
sqlite-devel # SQLite database
ncurses-devel # Terminal UI library
# CLI utilities (not in cargo/go/npm)
jq # JSON processor
tmux # Terminal multiplexer
tree # Directory listing
unzip # Archive extraction
ShellCheck # Shell script linter
socat # Multipurpose network relay
gnupg2 # GPG encryption/signing
)
install_dev_tools_group()
{
if dnf group list installed 2>/dev/null | grep -q "Development Tools"; then
msgr ok "@development-tools group already installed"
else
msgr run "Installing @development-tools group"
sudo dnf group install -y "Development Tools"
fi
}
install_packages()
{
local to_install=()
for pkg in "${packages[@]}"; do
pkg="${pkg%%#*}"
pkg="${pkg// /}"
[[ -z "$pkg" ]] && continue
if rpm -q "$pkg" &> /dev/null; then
msgr ok "$pkg already installed"
else
to_install+=("$pkg")
fi
done
if [[ ${#to_install[@]} -gt 0 ]]; then
msgr run "Installing ${#to_install[@]} packages: ${to_install[*]}"
sudo dnf install -y "${to_install[@]}"
else
msgr ok "All packages already installed"
fi
}
main()
{
install_dev_tools_group
install_packages
msgr yay "dnf package installations complete"
}
main "$@"