Compare commits
50 Commits
03cd4109fd
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b1564e73ca | |||
| 86c14ff05c | |||
| e6ca4f76e3 | |||
| 62e31f898d | |||
|
776578e770
|
|||
|
b29d9ce60d
|
|||
|
2ad7c1c03c
|
|||
|
4358918f3b
|
|||
| 9dcb193f58 | |||
| 9971f9c73f | |||
| f03f27cf88 | |||
| ff4d295285 | |||
| 8247a8c962 | |||
| 6a81bc8054 | |||
| bbd0d54e65 | |||
| 1b55f3f173 | |||
| 1b9d9424b4 | |||
| ac89fa5ec8 | |||
| 3ba83b8dd1 | |||
| e5202c601f | |||
| f634c3a9d1 | |||
| 3187e3e396 | |||
| 6883f67b74 | |||
| 991a82b833 | |||
| 431dffb1a6 | |||
| 96481590bc | |||
| f3496206eb | |||
| 76abbd7d1d | |||
| 980fa75310 | |||
| c76ed8e31e | |||
| 3ba293692e | |||
| 4b50becf6f | |||
| a245bebffb | |||
| b08c9acb29 | |||
| 2ef00a0dd9 | |||
| c9ccdf3086 | |||
| ebfb4b965d | |||
| 67504dccff | |||
| 73a38cfa55 | |||
| c366cd7789 | |||
| f824ca52d3 | |||
| f9ff648977 | |||
| 03150e539d | |||
| c11d846402 | |||
| 66cbb97f91 | |||
| 7fd2fd7317 | |||
| 087ffacfbd | |||
| 7bb8a3077c | |||
| 4cc4f2347e | |||
| ebdd73bb31 |
@@ -1,31 +0,0 @@
|
||||
# ~/.bash_aliases
|
||||
|
||||
alias ll='ls -alF --color=auto'
|
||||
alias la='ls -A --color=auto'
|
||||
alias l='ls -CF --color=auto'
|
||||
alias h='history'
|
||||
alias grep='grep --color=auto'
|
||||
alias d='docker'
|
||||
alias dc='docker compose'
|
||||
alias dotpull='echo "🔄 Updating dotfiles..." && git -C ~/.dotfiles pull && echo "✅ Done."'
|
||||
|
||||
alias reloadbash='source ~/.bashrc && echo "Bash config reloaded."'
|
||||
|
||||
linkdocker() {
|
||||
if [ -e ~/docker ] && [ ! -L ~/docker ]; then
|
||||
echo "~/docker exists and is not a symlink. Not replacing."
|
||||
return 1
|
||||
fi
|
||||
ln -sf /opt/docker ~/docker
|
||||
}
|
||||
|
||||
alias install_tailscale='curl -fsSL https://tailscale.com/install.sh | sh'
|
||||
alias hs_connect='sudo tailscale up --login-server https://headscale.ryans.tools'
|
||||
|
||||
alias profile='bash <(curl -sL https://ryans.tools/host_profile)'
|
||||
|
||||
alias install_qemuagent='sudo apt update && sudo apt install -y qemu-guest-agent && sudo systemctl enable --now qemu-guest-agent'
|
||||
alias install_stow='sudo apt install -y stow'
|
||||
alias install_neofetch='sudo apt install -y neofetch'
|
||||
alias memoryinfo='sudo dmidecode -t memory | grep -i "Type:\|Speed:\|Size:"'
|
||||
alias raminfo="sudo dmidecode -t memory | awk '/Memory Device/,/^$/' | grep -E 'Size:|Type:|Speed:'"
|
||||
@@ -1,9 +0,0 @@
|
||||
[user]
|
||||
name = Ryan Hamilton
|
||||
email = ryanehamil@outlook.com
|
||||
|
||||
[color]
|
||||
ui = auto
|
||||
|
||||
[core]
|
||||
editor = nano
|
||||
76
README.md
76
README.md
@@ -1,8 +1,82 @@
|
||||
# Ryan's Dotfiles
|
||||
|
||||
Minimal Bash dotfiles for SSH and remote environments.
|
||||
A comprehensive dotfiles repository for SSH, remote environments, and development setups.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
~/.dotfiles/
|
||||
├── stow/ # Configs organized for GNU Stow
|
||||
│ ├── bash/ # Bash configuration (.bashrc, .bash_aliases, .inputrc)
|
||||
│ ├── git/ # Git configuration (.gitconfig)
|
||||
│ └── [other configs] # Additional configurations as needed
|
||||
├── packages/
|
||||
│ ├── base.txt # MUST HAVE - Core system utilities
|
||||
│ ├── cli-tools.txt # Nice-to-haves - Enhanced CLI tools
|
||||
│ ├── dev.txt # Development tools (docker, node, etc)
|
||||
│ └── gui.txt # Desktop applications
|
||||
├── bootstrap.sh # Get repo and select branch
|
||||
├── setup.sh # Main orchestrator - runs all configuration
|
||||
└── README.md # Documentation
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
**From internet:**
|
||||
|
||||
```bash
|
||||
bash <(curl -sL https://ryans.tools/dotfiles)
|
||||
```
|
||||
|
||||
**From local copy (USB drive, etc.):**
|
||||
|
||||
```bash
|
||||
./bootstrap.sh
|
||||
```
|
||||
|
||||
The installer will:
|
||||
|
||||
1. **Bootstrap:** Clone repository (or use local copy), select branch if desired
|
||||
2. **Setup:** Check dependencies, backup conflicts, install packages, configure with GNU Stow
|
||||
|
||||
## Updates
|
||||
|
||||
**Regular updates (recommended):**
|
||||
|
||||
```bash
|
||||
dotpull
|
||||
```
|
||||
|
||||
**Branch switching or major re-setup:**
|
||||
|
||||
```bash
|
||||
cd ~/.dotfiles && ./bootstrap.sh
|
||||
```
|
||||
|
||||
## Package Categories
|
||||
|
||||
- **Base**: Essential system utilities (curl, git, stow, vim, etc.)
|
||||
- **CLI Tools**: Enhanced command-line tools (bat, fzf, ripgrep, etc.)
|
||||
- **Development**: Programming languages and dev tools (nodejs, python, docker, etc.)
|
||||
- **GUI**: Desktop applications (firefox, code, vlc, etc.)
|
||||
|
||||
## Updating
|
||||
|
||||
After initial installation, use the `dotpull` alias to update your dotfiles:
|
||||
|
||||
```bash
|
||||
dotpull
|
||||
```
|
||||
|
||||
This alias will pull the latest changes and re-stow all configurations.
|
||||
|
||||
## Manual Management
|
||||
|
||||
To manage individual configurations:
|
||||
|
||||
```bash
|
||||
cd ~/.dotfiles/stow
|
||||
stow -t ~ bash # Link bash config
|
||||
stow -t ~ git # Link git config
|
||||
stow -D bash # Unlink bash config
|
||||
```
|
||||
|
||||
84
bootstrap.sh
Normal file
84
bootstrap.sh
Normal file
@@ -0,0 +1,84 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
DOTFILES_REPO="https://gitea.purpleraft.com/ryan/dotfiles"
|
||||
DOTFILES_DIR="$HOME/.dotfiles"
|
||||
|
||||
echo "Starting dotfiles bootstrap..."
|
||||
|
||||
# Clone repo (new installs) or update for branch switching
|
||||
if [ -d "$DOTFILES_DIR/.git" ]; then
|
||||
echo "Updating existing dotfiles repo..."
|
||||
cd "$DOTFILES_DIR"
|
||||
|
||||
# Handle local changes if they exist
|
||||
if ! git diff-index --quiet HEAD --; then
|
||||
echo "Local changes detected:"
|
||||
echo "1) Discard 2) Stash 3) Abort"
|
||||
read -p "Choice (1-3): " choice
|
||||
case $choice in
|
||||
1) git reset --hard HEAD ;;
|
||||
2) git stash push -m "Bootstrap $(date +%Y%m%d-%H%M)" ;;
|
||||
*) echo "Aborted. Check 'git status' in $DOTFILES_DIR"; exit 1 ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
git pull --quiet || { echo "Error: Failed to update repository"; exit 1; }
|
||||
else
|
||||
echo "Cloning dotfiles into $DOTFILES_DIR..."
|
||||
git clone "$DOTFILES_REPO" "$DOTFILES_DIR"
|
||||
cd "$DOTFILES_DIR"
|
||||
fi
|
||||
|
||||
# Branch selection - allows choosing which version of dotfiles to install
|
||||
# - New installs: git clone defaults to 'main', but you can switch to any branch
|
||||
# - Existing installs: switch from current branch to a different one
|
||||
# - Only runs in interactive terminals (skipped in automated/CI environments)
|
||||
# if [ -t 0 ] && [ -t 1 ] && [ -d ".git" ]; then
|
||||
# current_branch=$(git branch --show-current 2>/dev/null || echo "unknown")
|
||||
# echo "Current branch: $current_branch"
|
||||
|
||||
# echo
|
||||
# read -p "Switch to a different branch? (y/N): " -n 1 -r
|
||||
# echo
|
||||
|
||||
# if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
# echo "Available branches:"
|
||||
# git branch -a --format="%(refname:short)" | grep -v "HEAD" | sed 's|^origin/||' | sort -u | nl -w2 -s'. '
|
||||
|
||||
# branches=($(git branch -a --format="%(refname:short)" | grep -v "HEAD" | sed 's|^origin/||' | sort -u))
|
||||
|
||||
# while true; do
|
||||
# read -p "Enter branch number (1-${#branches[@]}): " branch_num
|
||||
|
||||
# if [[ "$branch_num" =~ ^[0-9]+$ ]] && [ "$branch_num" -ge 1 ] && [ "$branch_num" -le "${#branches[@]}" ]; then
|
||||
# selected_branch="${branches[$((branch_num-1))]}"
|
||||
# echo "Switching to branch: $selected_branch"
|
||||
|
||||
# if git show-ref --verify --quiet "refs/heads/$selected_branch"; then
|
||||
# git checkout "$selected_branch"
|
||||
# elif git show-ref --verify --quiet "refs/remotes/origin/$selected_branch"; then
|
||||
# git checkout -b "$selected_branch" "origin/$selected_branch"
|
||||
# else
|
||||
# echo "Error: Branch $selected_branch not found"
|
||||
# exit 1
|
||||
# fi
|
||||
|
||||
# git pull --quiet || true
|
||||
# echo "Switched to branch: $selected_branch"
|
||||
# break
|
||||
# else
|
||||
# echo "Invalid selection. Please enter a number between 1 and ${#branches[@]}"
|
||||
# fi
|
||||
# done
|
||||
# fi
|
||||
# fi
|
||||
|
||||
# Hand off to setup
|
||||
if [ -f "setup.sh" ]; then
|
||||
echo "Starting setup..."
|
||||
bash setup.sh
|
||||
else
|
||||
echo "Error: No setup.sh found!"
|
||||
exit 1
|
||||
fi
|
||||
153
docs/git-1password-setup.md
Normal file
153
docs/git-1password-setup.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# Git + 1Password HTTPS Credential Helper
|
||||
|
||||
This setup allows Git to automatically fetch HTTPS credentials from 1Password without storing them locally.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **1Password CLI installed**: The `op` command should be available
|
||||
|
||||
- On Ubuntu/Debian: Install from 1Password's official repository
|
||||
- Package name: `1password-cli` (included in base.txt)
|
||||
|
||||
2. **1Password CLI authenticated**: You must be signed in to 1Password CLI
|
||||
|
||||
```bash
|
||||
op signin
|
||||
```
|
||||
|
||||
3. **jq installed**: For JSON parsing (included in base.txt)
|
||||
|
||||
## Setup
|
||||
|
||||
The credential helper is automatically configured in your `.gitconfig`:
|
||||
|
||||
```ini
|
||||
[credential]
|
||||
helper = !~/.dotfiles/scripts/git-credential-1password.sh
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Storing Credentials in 1Password
|
||||
|
||||
For each Git HTTPS remote you want to use, create an item in 1Password with:
|
||||
|
||||
1. **Title**: Include the hostname (e.g., "GitHub", "gitlab.example.com", "bitbucket.org")
|
||||
2. **Username field**: Your Git username
|
||||
3. **Password field**: Your Git password/token
|
||||
4. **URL field** (optional but recommended): The full HTTPS URL of the repository
|
||||
|
||||
#### Examples:
|
||||
|
||||
**GitHub Personal Access Token:**
|
||||
|
||||
- Title: "GitHub"
|
||||
- Username: your-github-username
|
||||
- Password: ghp_xxxxxxxxxxxxxxxxxxxx
|
||||
- URL: https://github.com
|
||||
|
||||
**GitLab Token:**
|
||||
|
||||
- Title: "gitlab.example.com"
|
||||
- Username: your-gitlab-username
|
||||
- Password: glpat-xxxxxxxxxxxxxxxxxxxx
|
||||
- URL: https://gitlab.example.com
|
||||
|
||||
### Using with Git
|
||||
|
||||
Once set up, Git operations will automatically prompt 1Password for credentials:
|
||||
|
||||
```bash
|
||||
# Clone a private repo
|
||||
git clone https://github.com/user/private-repo.git
|
||||
|
||||
# Push to origin
|
||||
git push origin main
|
||||
|
||||
# Add a new HTTPS remote
|
||||
git remote add upstream https://github.com/upstream/repo.git
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. When Git needs HTTPS credentials, it calls the credential helper
|
||||
2. The helper searches 1Password for items matching the hostname
|
||||
3. It looks for matches in:
|
||||
- URL fields containing the hostname
|
||||
- Item titles containing the hostname
|
||||
- Additional information containing the hostname
|
||||
4. Returns the username and password to Git
|
||||
5. Git uses these credentials for the operation
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "1Password CLI (op) not found"
|
||||
|
||||
Install 1Password CLI or ensure it's in your PATH:
|
||||
|
||||
```bash
|
||||
# Check if installed
|
||||
which op
|
||||
|
||||
# Install if missing (Ubuntu/Debian)
|
||||
curl -sS https://downloads.1password.com/linux/keys/1password.asc | sudo gpg --dearmor --output /usr/share/keyrings/1password-archive-keyring.gpg
|
||||
echo 'deb [arch=amd64 signed-by=/usr/share/keyrings/1password-archive-keyring.gpg] https://downloads.1password.com/linux/debian/amd64 stable main' | sudo tee /etc/apt/sources.list.d/1password.list
|
||||
sudo apt update && sudo apt install 1password-cli
|
||||
```
|
||||
|
||||
### "Not signed in to 1Password CLI"
|
||||
|
||||
Sign in to 1Password CLI:
|
||||
|
||||
```bash
|
||||
op signin
|
||||
```
|
||||
|
||||
### "No matching item found"
|
||||
|
||||
- Ensure the 1Password item title or URL contains the Git hostname
|
||||
- Check that the item has username and password fields
|
||||
- Try creating a new item with a clear title matching the hostname
|
||||
|
||||
### Test the Helper Manually
|
||||
|
||||
```bash
|
||||
# Test the credential helper directly
|
||||
echo -e "protocol=https\nhost=github.com\n" | ~/.dotfiles/scripts/git-credential-1password.sh get
|
||||
|
||||
# Debug: List all 1Password items to see what's available
|
||||
op item list --format=json | jq -r '.[] | "\(.title) - \(.id)"'
|
||||
|
||||
# Debug: See the structure of a specific item
|
||||
op item get "YOUR_ITEM_ID" --format=json | jq
|
||||
|
||||
# Debug: Check what fields are available in an item
|
||||
op item get "YOUR_ITEM_ID" --format=json | jq -r '.fields[] | "\(.label // .id): \(.value // "empty")"'
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
|
||||
**jq null matching errors:**
|
||||
|
||||
- This happens when 1Password items have missing fields
|
||||
- The updated script handles null values gracefully
|
||||
- Make sure your items have proper username and password fields
|
||||
|
||||
**Field naming issues:**
|
||||
|
||||
- The script looks for fields with labels containing: "username", "user", "login"
|
||||
- For passwords, it looks for: "password", "token", "secret", "pass"
|
||||
- If your fields have different names, rename them in 1Password
|
||||
|
||||
## Security Benefits
|
||||
|
||||
- Credentials are never stored in plain text on disk
|
||||
- Works with 1Password's security features (Touch ID, master password, etc.)
|
||||
- Credentials are fetched fresh each time (no caching)
|
||||
- Works seamlessly with existing 1Password setup
|
||||
|
||||
## Limitations
|
||||
|
||||
- Only works with HTTPS Git remotes (SSH remotes continue to use SSH keys)
|
||||
- Requires 1Password CLI to be signed in
|
||||
- May prompt for 1Password unlock depending on your security settings
|
||||
47
install.sh
47
install.sh
@@ -1,47 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
DOTFILES_REPO="https://gitea.purpleraft.com/ryan/dotfiles"
|
||||
DOTFILES_DIR="$HOME/.dotfiles"
|
||||
|
||||
declare -A FILES_TO_LINK=(
|
||||
[".bashrc"]="$HOME/.bashrc"
|
||||
[".bash_aliases"]="$HOME/.bash_aliases"
|
||||
[".inputrc"]="$HOME/.inputrc"
|
||||
[".gitconfig"]="$HOME/.gitconfig"
|
||||
)
|
||||
|
||||
# Clone or update the repo
|
||||
if [ -d "$DOTFILES_DIR/.git" ]; then
|
||||
echo "Updating existing dotfiles repo..."
|
||||
git -C "$DOTFILES_DIR" pull --quiet
|
||||
else
|
||||
echo "Cloning dotfiles into $DOTFILES_DIR..."
|
||||
git clone "$DOTFILES_REPO" "$DOTFILES_DIR"
|
||||
fi
|
||||
|
||||
# Symlink each file safely
|
||||
for file in "${!FILES_TO_LINK[@]}"; do
|
||||
target="${FILES_TO_LINK[$file]}"
|
||||
source="$DOTFILES_DIR/$file"
|
||||
|
||||
if [ -L "$target" ]; then
|
||||
echo "✔ Symlink already exists: $target"
|
||||
elif [ -e "$target" ]; then
|
||||
echo "⚠️ Backing up existing file: $target -> ${target}.bak"
|
||||
mv "$target" "${target}.bak"
|
||||
ln -s "$source" "$target"
|
||||
echo "🔗 Linked: $source → $target"
|
||||
else
|
||||
ln -s "$source" "$target"
|
||||
echo "🔗 Linked: $source → $target"
|
||||
fi
|
||||
done
|
||||
|
||||
# Optionally source the new bashrc
|
||||
if [[ $- == *i* ]]; then
|
||||
echo "Reloading Bash config..."
|
||||
source ~/.bashrc
|
||||
fi
|
||||
|
||||
echo "✅ Dotfiles install complete."
|
||||
14
packages/base.txt
Normal file
14
packages/base.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
# Base packages - MUST HAVE
|
||||
# Core system utilities
|
||||
curl
|
||||
wget
|
||||
git
|
||||
stow
|
||||
vim
|
||||
nano
|
||||
bash-completion
|
||||
tree
|
||||
htop
|
||||
unzip
|
||||
zip
|
||||
jq
|
||||
11
packages/cli-tools.txt
Normal file
11
packages/cli-tools.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
# CLI Tools - Nice-to-haves
|
||||
# Enhanced command line tools
|
||||
bat
|
||||
fd-find
|
||||
ripgrep
|
||||
fzf
|
||||
tmux
|
||||
screen
|
||||
jq
|
||||
ncdu
|
||||
exa
|
||||
15
packages/dev.txt
Normal file
15
packages/dev.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
# Development Tools
|
||||
# Programming languages and tools
|
||||
nodejs
|
||||
npm
|
||||
python3
|
||||
python3-pip
|
||||
docker
|
||||
docker-compose
|
||||
build-essential
|
||||
make
|
||||
gcc
|
||||
g++
|
||||
golang-go
|
||||
rust
|
||||
cargo
|
||||
6
packages/gui.txt
Normal file
6
packages/gui.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
# GUI Applications
|
||||
# Desktop applications (for systems with GUI)
|
||||
fonts-firacode
|
||||
code
|
||||
vlc
|
||||
terminator
|
||||
159
packages/install.sh
Normal file
159
packages/install.sh
Normal file
@@ -0,0 +1,159 @@
|
||||
#!/bin/bash
|
||||
# packages/install.sh - Module to install package lists
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [ $# -lt 1 ] || [ $# -gt 2 ]; then
|
||||
echo "Usage: $0 <dotfiles_directory> [group]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dotfiles_dir="$1"
|
||||
# Optional group argument (e.g., base.txt)
|
||||
group_arg="${2:-}"
|
||||
# Source shared utilities
|
||||
source "$dotfiles_dir/scripts/utils.sh"
|
||||
|
||||
# Detect package manager
|
||||
detect_package_manager() {
|
||||
if command_exists apt; then
|
||||
echo "apt"
|
||||
elif command_exists yum; then
|
||||
echo "yum"
|
||||
elif command_exists dnf; then
|
||||
echo "dnf"
|
||||
elif command_exists pacman; then
|
||||
echo "pacman"
|
||||
elif command_exists brew; then
|
||||
echo "brew"
|
||||
else
|
||||
echo "unknown"
|
||||
fi
|
||||
}
|
||||
|
||||
# Install packages with detected package manager
|
||||
install_packages() {
|
||||
local package_manager="$1"
|
||||
shift
|
||||
local packages=("$@")
|
||||
|
||||
if [ ${#packages[@]} -eq 0 ]; then
|
||||
log_info "No packages to install"
|
||||
return 0
|
||||
fi
|
||||
|
||||
case "$package_manager" in
|
||||
apt)
|
||||
log_info "Installing packages with apt..."
|
||||
sudo apt update && sudo apt install -y "${packages[@]}"
|
||||
;;
|
||||
yum)
|
||||
log_info "Installing packages with yum..."
|
||||
sudo yum install -y "${packages[@]}"
|
||||
;;
|
||||
dnf)
|
||||
log_info "Installing packages with dnf..."
|
||||
sudo dnf install -y "${packages[@]}"
|
||||
;;
|
||||
pacman)
|
||||
log_info "Installing packages with pacman..."
|
||||
sudo pacman -S --noconfirm "${packages[@]}"
|
||||
;;
|
||||
brew)
|
||||
log_info "Installing packages with brew..."
|
||||
brew install "${packages[@]}"
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown package manager. Please install packages manually."
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Read packages from file
|
||||
read_package_file() {
|
||||
local file="$1"
|
||||
if [ -f "$file" ]; then
|
||||
grep -v '^#' "$file" | grep -v '^[[:space:]]*$' | tr '\n' ' '
|
||||
fi
|
||||
}
|
||||
|
||||
# Loop through package lists and install
|
||||
install_package_lists() {
|
||||
local dotfiles_dir="$1"
|
||||
local packages_dir="$dotfiles_dir/packages"
|
||||
|
||||
if [ ! -d "$packages_dir" ]; then
|
||||
log_warning "No packages directory found at $packages_dir"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local package_manager
|
||||
package_manager=$(detect_package_manager)
|
||||
|
||||
if [ "$package_manager" = "unknown" ]; then
|
||||
log_error "No supported package manager found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Detected package manager: $package_manager"
|
||||
|
||||
# Define package group order
|
||||
local package_files=("base.txt" "cli-tools.txt" "dev.txt" "gui.txt")
|
||||
|
||||
# Determine which groups to install
|
||||
local selected_groups=()
|
||||
if [ -n "$group_arg" ]; then
|
||||
# Direct install of specified group
|
||||
if [[ " ${package_files[*]} " == *" $group_arg"* ]]; then
|
||||
selected_groups=("$group_arg")
|
||||
else
|
||||
log_error "Unknown package group: $group_arg"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Available package groups:"
|
||||
for pkg in "${package_files[@]}"; do
|
||||
if [ -f "$packages_dir/$pkg" ]; then
|
||||
echo " - $pkg"
|
||||
fi
|
||||
done
|
||||
read -rp "Enter groups to install (comma-separated or 'all'): " choice
|
||||
if [[ "$choice" =~ ^([Aa]ll)$ ]]; then
|
||||
selected_groups=("${package_files[@]}")
|
||||
else
|
||||
IFS=',' read -ra parts <<< "$choice"
|
||||
for part in "${parts[@]}"; do
|
||||
grp="${part// /}"
|
||||
if [[ " ${package_files[*]} " == *"$grp"* ]]; then
|
||||
selected_groups+=("$grp")
|
||||
else
|
||||
log_warning "Unknown package group: $grp"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
# Loop through selected package lists
|
||||
for package_file in "${selected_groups[@]}"; do
|
||||
file_path="$packages_dir/$package_file"
|
||||
if [ ! -f "$file_path" ]; then
|
||||
log_info "Package file $package_file not found, skipping"
|
||||
continue
|
||||
fi
|
||||
log_info "Installing packages from $package_file..."
|
||||
local packages
|
||||
packages=$(read_package_file "$file_path")
|
||||
|
||||
if [ -n "$packages" ]; then
|
||||
read -ra package_array <<< "$packages"
|
||||
install_packages "$package_manager" "${package_array[@]}"
|
||||
log_success "Completed installation from $package_file"
|
||||
else
|
||||
log_info "No packages found in $package_file"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Execute when run directly
|
||||
install_package_lists "$dotfiles_dir"
|
||||
57
scripts/00-check-dependencies.sh
Normal file
57
scripts/00-check-dependencies.sh
Normal file
@@ -0,0 +1,57 @@
|
||||
#!/bin/bash
|
||||
# scripts/00-check-dependencies.sh - Check for required dependencies
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Source utilities
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/utils.sh"
|
||||
|
||||
check_dependencies() {
|
||||
log_info "Checking dependencies..."
|
||||
|
||||
local missing_deps=()
|
||||
|
||||
# Check for git
|
||||
if ! command_exists git; then
|
||||
missing_deps+=("git")
|
||||
fi
|
||||
|
||||
# Check for stow
|
||||
if ! command_exists stow; then
|
||||
missing_deps+=("stow")
|
||||
fi
|
||||
|
||||
if [ ${#missing_deps[@]} -gt 0 ]; then
|
||||
log_error "Missing required dependencies: ${missing_deps[*]}"
|
||||
echo ""
|
||||
echo "Please install the missing dependencies:"
|
||||
|
||||
for dep in "${missing_deps[@]}"; do
|
||||
case "$dep" in
|
||||
"git")
|
||||
echo " Git:"
|
||||
echo " Ubuntu/Debian: sudo apt install git"
|
||||
echo " macOS: brew install git"
|
||||
echo " Or visit: https://git-scm.com/downloads"
|
||||
;;
|
||||
"stow")
|
||||
echo " GNU Stow:"
|
||||
echo " Ubuntu/Debian: sudo apt install stow"
|
||||
echo " macOS: brew install stow"
|
||||
echo " Or use the alias: install_stow"
|
||||
;;
|
||||
esac
|
||||
echo ""
|
||||
done
|
||||
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_success "All dependencies satisfied"
|
||||
}
|
||||
|
||||
# Run if called directly
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
check_dependencies
|
||||
fi
|
||||
65
scripts/10-backup-files.sh
Normal file
65
scripts/10-backup-files.sh
Normal file
@@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
# scripts/10-backup-files.sh - Backup existing files that would conflict
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Source utilities
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/utils.sh"
|
||||
|
||||
backup_files() {
|
||||
local dotfiles_dir="$1"
|
||||
|
||||
log_info "Checking for file conflicts..."
|
||||
|
||||
local backed_up=()
|
||||
|
||||
# Auto-discover files from stow directories
|
||||
if [ -d "$dotfiles_dir/stow" ]; then
|
||||
# Use new stow structure - scan all files in stow subdirectories
|
||||
for config_dir in "$dotfiles_dir/stow"/*; do
|
||||
if [ -d "$config_dir" ]; then
|
||||
log_info "Checking $(basename "$config_dir") configuration for conflicts..."
|
||||
|
||||
# Find all files that would be stowed
|
||||
find "$config_dir" -type f | while read -r file; do
|
||||
# Get relative path from config directory
|
||||
relative_file="${file#$config_dir/}"
|
||||
target_file="$HOME/$relative_file"
|
||||
|
||||
if [ -f "$target_file" ] && [ ! -L "$target_file" ]; then
|
||||
log_warning "Backing up existing file: $target_file -> ${target_file}.bak"
|
||||
mv "$target_file" "${target_file}.bak"
|
||||
backed_up+=("$relative_file")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
else
|
||||
# Fallback to old structure - hardcoded file list
|
||||
local files_to_check=(.bashrc .bash_aliases .inputrc .gitconfig)
|
||||
|
||||
for file in "${files_to_check[@]}"; do
|
||||
if [ -f "$HOME/$file" ] && [ ! -L "$HOME/$file" ]; then
|
||||
log_warning "Backing up existing file: $HOME/$file -> $HOME/${file}.bak"
|
||||
mv "$HOME/$file" "$HOME/${file}.bak"
|
||||
backed_up+=("$file")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ ${#backed_up[@]} -gt 0 ]; then
|
||||
log_info "Backed up ${#backed_up[@]} file(s): ${backed_up[*]}"
|
||||
else
|
||||
log_info "No file conflicts found"
|
||||
fi
|
||||
}
|
||||
|
||||
# Run if called directly
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
if [ $# -ne 1 ]; then
|
||||
log_error "Usage: $0 <dotfiles_directory>"
|
||||
exit 1
|
||||
fi
|
||||
backup_files "$1"
|
||||
fi
|
||||
59
scripts/20-setup-stow.sh
Normal file
59
scripts/20-setup-stow.sh
Normal file
@@ -0,0 +1,59 @@
|
||||
#!/bin/bash
|
||||
# scripts/20-setup-stow.sh - Handle GNU Stow operations
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Source utilities
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/utils.sh"
|
||||
|
||||
setup_stow() {
|
||||
local dotfiles_dir="$1"
|
||||
|
||||
if [ -d "$dotfiles_dir/stow" ]; then
|
||||
# Use new stow structure - stow each subdirectory individually
|
||||
cd "$dotfiles_dir/stow"
|
||||
log_info "Using Stow to symlink dotfiles from stow/ subdirectories..."
|
||||
|
||||
local stowed=()
|
||||
|
||||
for config_dir in *; do
|
||||
if [ -d "$config_dir" ]; then
|
||||
log_info "Stowing $config_dir configuration..."
|
||||
|
||||
if ! stow --adopt -t "$HOME" "$config_dir" 2>/dev/null; then
|
||||
log_warning "Adopting failed for $config_dir, trying regular stow..."
|
||||
stow -t "$HOME" "$config_dir"
|
||||
fi
|
||||
|
||||
stowed+=("$config_dir")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#stowed[@]} -gt 0 ]; then
|
||||
log_success "Stowed ${#stowed[@]} configuration(s): ${stowed[*]}"
|
||||
else
|
||||
log_warning "No stow subdirectories found in $dotfiles_dir/stow"
|
||||
fi
|
||||
else
|
||||
# Fallback to old structure - stow entire directory
|
||||
cd "$dotfiles_dir"
|
||||
log_info "Using Stow to symlink dotfiles from root directory..."
|
||||
|
||||
if ! stow --adopt -t "$HOME" . 2>/dev/null; then
|
||||
log_warning "Adopting failed, trying regular stow..."
|
||||
stow -t "$HOME" .
|
||||
fi
|
||||
|
||||
log_success "Stow setup complete"
|
||||
fi
|
||||
}
|
||||
|
||||
# Run if called directly
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
if [ $# -ne 1 ]; then
|
||||
log_error "Usage: $0 <dotfiles_directory>"
|
||||
exit 1
|
||||
fi
|
||||
setup_stow "$1"
|
||||
fi
|
||||
27
scripts/30-install-packages.sh
Normal file
27
scripts/30-install-packages.sh
Normal file
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
# scripts/30-install-packages.sh - Wrapper to invoke packages module
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Source utilities
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/utils.sh"
|
||||
|
||||
# Run module when called directly
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
if [ $# -ne 1 ] && [ $# -ne 2 ]; then
|
||||
log_error "Usage: $0 <dotfiles_directory> [group]"
|
||||
exit 1
|
||||
fi
|
||||
dotfiles_dir="$1"
|
||||
module_script="$dotfiles_dir/packages/install.sh"
|
||||
# Group to install, default to base.txt
|
||||
group_arg="${2:-base.txt}"
|
||||
|
||||
if [ ! -f "$module_script" ]; then
|
||||
log_error "Module script not found: $module_script"
|
||||
exit 1
|
||||
fi
|
||||
# Always invoke module with desired group
|
||||
bash "$module_script" "$dotfiles_dir" "$group_arg"
|
||||
fi
|
||||
26
scripts/90-post-install.sh
Normal file
26
scripts/90-post-install.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
# scripts/90-post-install.sh - Post-installation tasks
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Source utilities
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/utils.sh"
|
||||
|
||||
post_install() {
|
||||
log_info "Running post-installation tasks..."
|
||||
|
||||
# Source the new bashrc if in interactive shell
|
||||
if is_interactive && [ -f "$HOME/.bashrc" ]; then
|
||||
log_info "Reloading Bash config..."
|
||||
source "$HOME/.bashrc"
|
||||
fi
|
||||
|
||||
# Show completion message
|
||||
log_success "Post-installation tasks complete"
|
||||
}
|
||||
|
||||
# Run if called directly
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
post_install
|
||||
fi
|
||||
162
scripts/git-credential-1password.sh
Executable file
162
scripts/git-credential-1password.sh
Executable file
@@ -0,0 +1,162 @@
|
||||
#!/bin/bash
|
||||
# Git credential helper for 1Password CLI
|
||||
# This script integrates with Git's credential system to fetch credentials from 1Password
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Source utilities for logging
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
if [ -f "$SCRIPT_DIR/utils.sh" ]; then
|
||||
source "$SCRIPT_DIR/utils.sh"
|
||||
fi
|
||||
|
||||
# Function to check if 1Password CLI is available and signed in
|
||||
check_op_cli() {
|
||||
if ! command -v op >/dev/null 2>&1; then
|
||||
echo "Error: 1Password CLI (op) not found. Please install it first." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check if signed in
|
||||
if ! op account list >/dev/null 2>&1; then
|
||||
echo "Error: Not signed in to 1Password CLI. Please run 'op signin' first." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Parse Git credential input
|
||||
parse_input() {
|
||||
while IFS= read -r line; do
|
||||
if [ -z "$line" ]; then
|
||||
break
|
||||
fi
|
||||
|
||||
case "$line" in
|
||||
protocol=*)
|
||||
protocol="${line#protocol=}"
|
||||
;;
|
||||
host=*)
|
||||
host="${line#host=}"
|
||||
;;
|
||||
username=*)
|
||||
username="${line#username=}"
|
||||
;;
|
||||
password=*)
|
||||
password="${line#password=}"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# Get credentials from 1Password
|
||||
get_credentials() {
|
||||
local search_term="$1"
|
||||
|
||||
# Try to find item by URL/host first
|
||||
local item_uuid
|
||||
if ! item_uuid=$(op item list --format=json 2>/dev/null | jq -r --arg host "$search_term" '
|
||||
.[] | select(
|
||||
((.urls // [])[] | select(.href != null and (.href | type) == "string") | .href | test($host; "i")) or
|
||||
((.title // "") | type == "string" and test($host; "i")) or
|
||||
((.additional_information // "") | type == "string" and test($host; "i"))
|
||||
) | .id' | head -1 2>/dev/null); then
|
||||
# If the complex search fails, try a simpler approach
|
||||
item_uuid=""
|
||||
fi
|
||||
|
||||
if [ -z "$item_uuid" ]; then
|
||||
# Fallback: simple search by title containing the host
|
||||
if ! item_uuid=$(op item list --format=json 2>/dev/null | jq -r --arg host "$search_term" '
|
||||
.[] | select((.title // "") != "" and ((.title // "") | type == "string") and ((.title // "") | test($host; "i"))) | .id' | head -1 2>/dev/null); then
|
||||
item_uuid=""
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$item_uuid" ]; then
|
||||
echo "No matching item found in 1Password for: $search_term" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Get the item details
|
||||
local item_json
|
||||
if ! item_json=$(op item get "$item_uuid" --format=json 2>/dev/null); then
|
||||
echo "Failed to retrieve item from 1Password" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Extract username and password
|
||||
local op_username op_password
|
||||
op_username=$(echo "$item_json" | jq -r '(.fields // [])[] | select((.id // "") == "username" or ((.label // "") | type == "string" and test("username"; "i"))) | .value // empty' | head -1 2>/dev/null)
|
||||
op_password=$(echo "$item_json" | jq -r '(.fields // [])[] | select((.id // "") == "password" or ((.label // "") | type == "string" and test("password|token"; "i"))) | .value // empty' | head -1 2>/dev/null)
|
||||
|
||||
# If standard fields don't work, try common field names
|
||||
if [ -z "$op_username" ]; then
|
||||
op_username=$(echo "$item_json" | jq -r '(.fields // [])[] | select((.label // "") | type == "string" and test("user|login"; "i")) | .value // empty' | head -1 2>/dev/null)
|
||||
fi
|
||||
|
||||
if [ -z "$op_password" ]; then
|
||||
op_password=$(echo "$item_json" | jq -r '(.fields // [])[] | select((.label // "") | type == "string" and test("pass|token|secret"; "i")) | .value // empty' | head -1 2>/dev/null)
|
||||
fi
|
||||
|
||||
if [ -z "$op_username" ] || [ -z "$op_password" ]; then
|
||||
echo "Username or password not found in 1Password item" >&2
|
||||
echo "Available fields:" >&2
|
||||
echo "$item_json" | jq -r '(.fields // [])[] | " - \(.label // .id // "unknown")"' 2>/dev/null >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "username=$op_username"
|
||||
echo "password=$op_password"
|
||||
}
|
||||
|
||||
# Main credential helper logic
|
||||
case "${1:-}" in
|
||||
get)
|
||||
# Initialize variables
|
||||
protocol=""
|
||||
host=""
|
||||
username=""
|
||||
password=""
|
||||
|
||||
# Parse input from Git
|
||||
parse_input
|
||||
|
||||
# Only handle HTTPS requests
|
||||
if [ "$protocol" != "https" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check 1Password CLI availability
|
||||
if ! check_op_cli; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Search for credentials
|
||||
if [ -n "$host" ]; then
|
||||
if get_credentials "$host"; then
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# If we get here, no credentials were found
|
||||
exit 1
|
||||
;;
|
||||
|
||||
store)
|
||||
# We don't store credentials in 1Password via this helper
|
||||
# Users should add them manually to 1Password
|
||||
exit 0
|
||||
;;
|
||||
|
||||
erase)
|
||||
# We don't erase credentials from 1Password via this helper
|
||||
exit 0
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Usage: $0 {get|store|erase}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
82
scripts/utils.sh
Normal file
82
scripts/utils.sh
Normal file
@@ -0,0 +1,82 @@
|
||||
#!/bin/bash
|
||||
# scripts/utils.sh - Shared utilities and functions
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Logging functions
|
||||
log_info() {
|
||||
echo -e "${BLUE}ℹ️ $1${NC}"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}✅ $1${NC}"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}⚠️ $1${NC}"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}❌ $1${NC}"
|
||||
}
|
||||
|
||||
# Check if command exists
|
||||
command_exists() {
|
||||
command -v "$1" &> /dev/null
|
||||
}
|
||||
|
||||
# Check if we're in an interactive shell
|
||||
is_interactive() {
|
||||
[[ $- == *i* ]]
|
||||
}
|
||||
|
||||
# Branch selection utilities
|
||||
list_branches() {
|
||||
local dotfiles_dir="$1"
|
||||
cd "$dotfiles_dir"
|
||||
|
||||
# Get available branches (both local and remote)
|
||||
local branches=()
|
||||
while IFS= read -r branch; do
|
||||
# Clean up branch names (remove origin/ prefix, etc.)
|
||||
clean_branch=$(echo "$branch" | sed 's|^origin/||' | sed 's|^remotes/origin/||')
|
||||
# Skip HEAD and duplicates
|
||||
if [[ "$clean_branch" != "HEAD" ]] && [[ ! " ${branches[@]} " =~ " ${clean_branch} " ]]; then
|
||||
branches+=("$clean_branch")
|
||||
fi
|
||||
done < <(git branch -a --format="%(refname:short)" | grep -v "HEAD")
|
||||
|
||||
# Sort and return branches
|
||||
printf '%s\n' "${branches[@]}" | sort
|
||||
}
|
||||
|
||||
switch_to_branch() {
|
||||
local dotfiles_dir="$1"
|
||||
local selected_branch="$2"
|
||||
|
||||
cd "$dotfiles_dir"
|
||||
|
||||
log_info "Switching to branch: $selected_branch"
|
||||
|
||||
if git show-ref --verify --quiet "refs/heads/$selected_branch"; then
|
||||
# Local branch exists
|
||||
git checkout "$selected_branch"
|
||||
elif git show-ref --verify --quiet "refs/remotes/origin/$selected_branch"; then
|
||||
# Remote branch exists, create local tracking branch
|
||||
git checkout -b "$selected_branch" "origin/$selected_branch"
|
||||
else
|
||||
log_error "Branch $selected_branch not found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Pull latest changes
|
||||
log_info "Pulling latest changes..."
|
||||
git pull --quiet || true
|
||||
|
||||
log_success "Switched to branch: $selected_branch"
|
||||
}
|
||||
53
setup.sh
Normal file
53
setup.sh
Normal file
@@ -0,0 +1,53 @@
|
||||
#!/bin/bash
|
||||
# setup.sh - Main dotfiles setup orchestrator
|
||||
# Runs all numbered scripts in sequence
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Get the absolute path to the directory containing this script
|
||||
# This handles symlinks and relative paths to always find the dotfiles root
|
||||
DOTFILES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Source utilities with fallback
|
||||
if [ -f "$DOTFILES_DIR/scripts/utils.sh" ]; then
|
||||
source "$DOTFILES_DIR/scripts/utils.sh"
|
||||
else
|
||||
echo "Error: scripts/utils.sh not found! Repository structure is corrupted."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "🔧 Starting dotfiles setup..."
|
||||
log_info "Working directory: $DOTFILES_DIR"
|
||||
|
||||
# Auto-discover and run setup scripts
|
||||
if [ -d "$DOTFILES_DIR/scripts" ]; then
|
||||
log_info "Running setup scripts..."
|
||||
|
||||
script_count=0
|
||||
# Find and run all numbered scripts using shell globbing
|
||||
for script in "$DOTFILES_DIR/scripts"/[0-9][0-9]-*.sh; do
|
||||
if [ -f "$script" ]; then
|
||||
script_name=$(basename "$script")
|
||||
log_info "Running $script_name..."
|
||||
# Execute each script in a separate shell for modularity
|
||||
if bash "$script" "$DOTFILES_DIR"; then
|
||||
log_success "✓ $script_name completed"
|
||||
else
|
||||
log_error "✗ $script_name failed"
|
||||
exit 1
|
||||
fi
|
||||
((++script_count))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $script_count -eq 0 ]; then
|
||||
log_warning "No numbered scripts found in scripts/ directory"
|
||||
else
|
||||
log_success "Completed $script_count setup scripts"
|
||||
fi
|
||||
else
|
||||
log_error "No scripts directory found! Something went wrong with the installation."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "🎉 Dotfiles setup complete!"
|
||||
194
stow/bash/.bash_aliases
Normal file
194
stow/bash/.bash_aliases
Normal file
@@ -0,0 +1,194 @@
|
||||
# ~/.bash_aliases
|
||||
|
||||
alias ls='ls --color=auto'
|
||||
alias ll='ls -alF --color=auto'
|
||||
alias la='ls -A --color=auto'
|
||||
alias l='ls -CF --color=auto'
|
||||
alias h='history'
|
||||
alias grep='grep --color=auto'
|
||||
alias d='docker'
|
||||
alias dc='docker compose'
|
||||
alias dotpull='echo "🔄 Updating dotfiles..." && git -C ~/.dotfiles pull && echo "🔗 Re-stowing dotfiles..." && bash ~/.dotfiles/scripts/20-setup-stow.sh ~/.dotfiles && echo "✅ Done."'
|
||||
|
||||
alias reloadbash='source ~/.bashrc && echo "Bash config reloaded."'
|
||||
|
||||
linkdocker() {
|
||||
if [ -e ~/docker ] && [ ! -L ~/docker ]; then
|
||||
echo "~/docker exists and is not a symlink. Not replacing."
|
||||
return 1
|
||||
fi
|
||||
ln -sf /opt/docker ~/docker
|
||||
}
|
||||
|
||||
alias install_tailscale='curl -fsSL https://tailscale.com/install.sh | sh'
|
||||
alias hs_connect='sudo tailscale up --login-server https://headscale.ryans.tools'
|
||||
|
||||
alias profile='bash <(curl -sL https://ryans.tools/host_profile)'
|
||||
|
||||
alias install_qemuagent='sudo apt update && sudo apt install -y qemu-guest-agent && sudo systemctl enable --now qemu-guest-agent'
|
||||
alias install_stow='sudo apt install -y stow'
|
||||
alias install_neofetch='sudo apt install -y neofetch'
|
||||
alias memoryinfo='sudo dmidecode -t memory | grep -i "Type:\|Speed:\|Size:"'
|
||||
alias install_packages='bash ~/.dotfiles/packages/install.sh ~/.dotfiles'
|
||||
alias update_list='sudo apt update && sudo apt list --upgradable'
|
||||
|
||||
# Git + 1Password helpers
|
||||
alias test_git_1p='echo "Testing Git 1Password credential helper for github.com:" && echo -e "protocol=https\nhost=github.com\n" | ~/.dotfiles/scripts/git-credential-1password.sh get'
|
||||
alias op_signin='op signin'
|
||||
alias op_list='op item list --format=json | jq -r ".[] | \"\(.title) - \(.id)\""'
|
||||
alias op_debug_item='function _op_debug() { op item get "$1" --format=json | jq -r ".fields[] | \"\(.label // .id): \(.value // \"empty\")\""; }; _op_debug'
|
||||
|
||||
alias dps='docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}"'
|
||||
alias fzfdlogs='docker logs $(docker ps --format "{{.Names}}" | fzf)'
|
||||
alias fzfdrestart='docker restart $(docker ps --format "{{.Names}}" | fzf)'
|
||||
|
||||
# Password generator: passgen
|
||||
# Usage examples:
|
||||
# passgen # 24 chars, a-zA-Z0-9, 1 password
|
||||
# passgen -l 40 -c 5 # 5 passwords, length 40
|
||||
# passgen --symbols # include safe symbols for .env
|
||||
# passgen --no-upper # remove uppercase
|
||||
# passgen --exclude '$"' # exclude specific chars
|
||||
# passgen --ambiguous # remove ambiguous chars (O0Il1)
|
||||
# passgen --strict # enforce "at least one of each selected class"
|
||||
|
||||
passgen() {
|
||||
local length=24 count=1
|
||||
local use_upper=1 use_lower=1 use_digits=1 use_symbols=0
|
||||
local strict=0 ambiguous=0
|
||||
local exclude=""
|
||||
local safe_symbols="!@#%^&*()_+.-" # .env-friendly (no quotes, $, `, \, =, :, space)
|
||||
local symbol_set="$safe_symbols" # can be extended later if you want a "wide" set
|
||||
|
||||
# parse args
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-l|--length) length="${2:-}"; shift 2 ;;
|
||||
-c|--count) count="${2:-}"; shift 2 ;;
|
||||
--no-upper) use_upper=0; shift ;;
|
||||
--no-lower) use_lower=0; shift ;;
|
||||
--no-digits) use_digits=0; shift ;;
|
||||
--symbols) use_symbols=1; shift ;;
|
||||
--no-symbols) use_symbols=0; shift ;;
|
||||
--exclude) exclude="${2:-}"; shift 2 ;;
|
||||
--ambiguous) ambiguous=1; shift ;;
|
||||
--strict) strict=1; shift ;;
|
||||
-h|--help)
|
||||
cat <<'EOF'
|
||||
passgen - flexible password generator
|
||||
|
||||
Options:
|
||||
-l, --length N Length of each password (default 24)
|
||||
-c, --count N How many passwords to generate (default 1)
|
||||
--no-upper Exclude A-Z
|
||||
--no-lower Exclude a-z
|
||||
--no-digits Exclude 0-9
|
||||
--symbols Include .env-safe symbols (!@#%^&*()_+.-)
|
||||
--no-symbols Exclude symbols (default)
|
||||
--exclude "xyz" Remove these characters from output
|
||||
--ambiguous Remove ambiguous characters: O 0 I l 1
|
||||
--strict Ensure each selected class appears at least once
|
||||
-h, --help Show this help
|
||||
|
||||
Notes:
|
||||
- Default charset: a-z A-Z 0-9
|
||||
- Symbols are .env-safe by default (no quotes, $, `, \, =, :, space)
|
||||
EOF
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
echo "passgen: unknown option: $1" >&2; return 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# sanity checks
|
||||
[[ "$length" =~ ^[0-9]+$ ]] || { echo "passgen: length must be a number" >&2; return 2; }
|
||||
[[ "$count" =~ ^[0-9]+$ ]] || { echo "passgen: count must be a number" >&2; return 2; }
|
||||
|
||||
# build class strings explicitly (no tr bracket weirdness)
|
||||
local UPPER="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
local LOWER="abcdefghijklmnopqrstuvwxyz"
|
||||
local DIGIT="0123456789"
|
||||
local SYMS="$symbol_set"
|
||||
|
||||
# optionally remove ambiguous chars
|
||||
if (( ambiguous )); then
|
||||
UPPER=${UPPER//O/}
|
||||
UPPER=${UPPER//I/}
|
||||
LOWER=${LOWER//l/}
|
||||
DIGIT=${DIGIT//0/}
|
||||
DIGIT=${DIGIT//1/}
|
||||
fi
|
||||
|
||||
# compose allowed set
|
||||
local allowed=""
|
||||
(( use_upper )) && allowed+="$UPPER"
|
||||
(( use_lower )) && allowed+="$LOWER"
|
||||
(( use_digits )) && allowed+="$DIGIT"
|
||||
(( use_symbols ))&& allowed+="$SYMS"
|
||||
|
||||
# if nothing selected, default to alnum
|
||||
if [[ -z "$allowed" ]]; then
|
||||
allowed="$UPPER$LOWER$DIGIT"
|
||||
fi
|
||||
|
||||
# remove any user-excluded chars from allowed (do this via tr to avoid globbing issues)
|
||||
if [[ -n "$exclude" ]]; then
|
||||
# printf each char of allowed, delete excluded via tr, then reassemble
|
||||
allowed="$(printf %s "$allowed" | tr -d "$exclude")"
|
||||
fi
|
||||
|
||||
# final check
|
||||
if [[ -z "$allowed" ]]; then
|
||||
echo "passgen: allowed set is empty after exclusions; loosen options." >&2
|
||||
return 2
|
||||
fi
|
||||
|
||||
# helper: generate one password meeting optional strict policy
|
||||
_gen_one() {
|
||||
local need_upper=$use_upper
|
||||
local need_lower=$use_lower
|
||||
local need_digit=$use_digits
|
||||
local need_symbol=$use_symbols
|
||||
local out=""
|
||||
|
||||
# seed with at least one of each selected class if --strict
|
||||
if (( strict )); then
|
||||
if (( need_upper )); then out+="${UPPER:RANDOM%${#UPPER}:1}"; fi
|
||||
if (( need_lower )); then out+="${LOWER:RANDOM%${#LOWER}:1}"; fi
|
||||
if (( need_digit )); then out+="${DIGIT:RANDOM%${#DIGIT}:1}"; fi
|
||||
if (( need_symbol )); then out+="${SYMS:RANDOM%${#SYMS}:1}"; fi
|
||||
fi
|
||||
|
||||
# fill the rest from the full allowed set, using /dev/urandom
|
||||
local needed=$(( length - ${#out} ))
|
||||
if (( needed > 0 )); then
|
||||
# draw more than needed, then cut (helps when exclusions reduce size)
|
||||
local draw=$(( needed * 3 ))
|
||||
local extra="$(LC_ALL=C tr -dc "$allowed" </dev/urandom | head -c "$draw")"
|
||||
out+="${extra:0:needed}"
|
||||
fi
|
||||
|
||||
# If exclusions or strict left us short (very rare), top-up in a loop.
|
||||
while (( ${#out} < length )); do
|
||||
out+=$(LC_ALL=C tr -dc "$allowed" </dev/urandom | head -c 1)
|
||||
done
|
||||
|
||||
# FisherYates shuffle so strict-class seeds aren't predictable up front
|
||||
local i j tmp arr=()
|
||||
for (( i=0; i<length; i++ )); do arr[i]="${out:i:1}"; done
|
||||
for (( i=length-1; i>0; i-- )); do
|
||||
# get a random index j using bytes from /dev/urandom
|
||||
j=$(( $(od -An -N2 -tu2 < /dev/urandom) % (i+1) ))
|
||||
tmp=${arr[i]}; arr[i]=${arr[j]}; arr[j]=$tmp
|
||||
done
|
||||
printf "%s" "${arr[*]}" | tr -d ' '
|
||||
}
|
||||
|
||||
# generate the requested count
|
||||
local i
|
||||
for (( i=1; i<=count; i++ )); do
|
||||
_gen_one
|
||||
printf "\n"
|
||||
done
|
||||
}
|
||||
@@ -23,3 +23,6 @@ shopt -s histappend
|
||||
if [ -f /etc/bash_completion ]; then
|
||||
. /etc/bash_completion
|
||||
fi
|
||||
|
||||
export SSH_AUTH_SOCK="/run/user/$UID/1password/agent.sock"
|
||||
|
||||
18
stow/git/.gitconfig
Normal file
18
stow/git/.gitconfig
Normal file
@@ -0,0 +1,18 @@
|
||||
[user]
|
||||
name = Ryan Hamilton
|
||||
email = ryanehamil@outlook.com
|
||||
signingkey = ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAHGLrr5MQeHZzvHJDH6qIbb3dWBxlf1YNUPhqI7lnFC
|
||||
|
||||
[color]
|
||||
ui = auto
|
||||
|
||||
[core]
|
||||
editor = nano
|
||||
[gpg]
|
||||
format = ssh
|
||||
[gpg "ssh"]
|
||||
program = /opt/1Password/op-ssh-sign
|
||||
[commit]
|
||||
gpgsign = true
|
||||
[credential]
|
||||
helper = !~/.dotfiles/scripts/git-credential-1password.sh
|
||||
88
stow/ssh/.ssh/config
Normal file
88
stow/ssh/.ssh/config
Normal file
@@ -0,0 +1,88 @@
|
||||
# Added by 1Password SSH Agent
|
||||
Host *
|
||||
IdentityAgent ~/.1password/agent.sock
|
||||
|
||||
# Currently RackNerd, using tailscale
|
||||
Host PurpleRaft_BASTION
|
||||
HostName ddns.ryans.tools
|
||||
User ryan
|
||||
|
||||
# PurpleRaft cloud servers
|
||||
Host "PurpleRaft_RackNerd"
|
||||
HostName ddns.ryans.tools
|
||||
User ryan
|
||||
|
||||
Host "PurpleRaft_ColoCrossing"
|
||||
HostName 204.44.100.13w
|
||||
User ryan
|
||||
|
||||
# PurpleRaft non-cloud servers, accessed via bastion
|
||||
Host "PurpleRaft_LazuliGate_viaRackNerd"
|
||||
ProxyJump "PurpleRaft_BASTION"
|
||||
HostName lazuligate.tail.ryans.tools
|
||||
User ryan
|
||||
|
||||
Host "PurpleRaft_LunarEcho_viaRackNerd"
|
||||
ProxyJump "PurpleRaft_BASTION"
|
||||
HostName lunarecho.tail.ryans.tools
|
||||
User ryan
|
||||
|
||||
Host "PurpleRaft_Titan_viaRackNerd"
|
||||
ProxyJump "PurpleRaft_BASTION"
|
||||
HostName titan.tail.ryans.tools
|
||||
User ryan
|
||||
|
||||
Host "PurpleRaft_GoldenWindow_viaRackNerd"
|
||||
ProxyJump "PurpleRaft_BASTION"
|
||||
HostName goldenwindow.tail.ryans.tools
|
||||
User ryan
|
||||
|
||||
Host "PurpleRaft_AtomicMidnight_viaRackNerd"
|
||||
ProxyJump "PurpleRaft_BASTION"
|
||||
HostName atomicmidnight.tail.ryans.tools
|
||||
User ryan
|
||||
|
||||
Host "PurpleRaft_WarpCore_viaRackNerd"
|
||||
ProxyJump "PurpleRaft_BASTION"
|
||||
HostName warpcore.tail.ryans.tools
|
||||
User ryan
|
||||
|
||||
Host "PurpleRaft_Typhoon_viaRackNerd"
|
||||
ProxyJump "PurpleRaft_BASTION"
|
||||
HostName typhoon.internal.purpleraft.com
|
||||
User ryan
|
||||
|
||||
# PurpleRaft non-cloud servers, accessed via tailnet
|
||||
Host "PurpleRaft_LazuliGate_viaTailNet"
|
||||
HostName lazuligate.tail.ryans.tools
|
||||
User ryan
|
||||
|
||||
Host "PurpleRaft_LunarEcho_viaTailNet"
|
||||
HostName lunarecho.tail.ryans.tools
|
||||
User ryan
|
||||
|
||||
Host "PurpleRaft_Titan_viaTailNet"
|
||||
HostName titan.tail.ryans.tools
|
||||
User ryan
|
||||
|
||||
Host "PurpleRaft_GoldenWindow_viaTailNet"
|
||||
HostName goldenwindow.tail.ryans.tools
|
||||
User ryan
|
||||
|
||||
Host "PurpleRaft_AtomicMidnight_viaTailNet"
|
||||
HostName atomicmidnight.tail.ryans.tools
|
||||
User ryan
|
||||
|
||||
Host "PurpleRaft_WarpCore_viaTailNet"
|
||||
HostName warpcore.tail.ryans.tools
|
||||
User ryan
|
||||
|
||||
Host "PurpleRaft_Typhoon_viaTailNet"
|
||||
HostName typhoon.internal.purpleraft.com
|
||||
User ryan
|
||||
|
||||
# Other
|
||||
Host "Gitea (Only for Git)"
|
||||
HostName gitea.cobalt.purpleraft.com
|
||||
Port 2001
|
||||
User ryan
|
||||
Reference in New Issue
Block a user