1
0

Compare commits

...

50 Commits

Author SHA1 Message Date
b1564e73ca Update stow/bash/.bash_aliases 2025-10-27 21:51:52 +00:00
86c14ff05c Update stow/bash/.bash_aliases 2025-08-30 04:32:50 +00:00
e6ca4f76e3 Update packages/base.txt 2025-08-24 17:39:18 +00:00
62e31f898d Update stow/bash/.bash_aliases 2025-08-11 01:33:22 +00:00
776578e770 feat: enhance Git 1Password integration with debugging commands and common issue guidance 2025-08-07 08:22:58 -05:00
b29d9ce60d feat: set SSH_AUTH_SOCK for 1Password agent in .bashrc 2025-08-07 08:18:36 -05:00
2ad7c1c03c feat: add Git and 1Password credential helper and update configurations 2025-08-07 07:50:09 -05:00
4358918f3b feat: update .gitconfig to include GPG signing configuration 2025-08-07 07:36:39 -05:00
9dcb193f58 feat: add alias update_list to show upgradable packages 2025-08-07 07:20:11 -05:00
9971f9c73f fix: update SSH config to remove invalid characters 2025-08-05 08:31:18 -05:00
f03f27cf88 Update stow/ssh/.ssh/config 2025-08-05 12:53:43 +00:00
ff4d295285 feat: add SSH configuration for 1Password SSH Agent 2025-08-05 07:44:52 -05:00
8247a8c962 fix: remove unnecessary GUI applications from package list 2025-08-05 07:39:27 -05:00
6a81bc8054 fix: update dotpull alias to use the correct stow script for re-stowing dotfiles 2025-08-05 00:15:01 -05:00
bbd0d54e65 fix: update install_packages alias to point to the correct script path 2025-08-05 00:14:11 -05:00
1b55f3f173 fix: update usage instructions to support optional group argument in package installation scripts 2025-08-05 00:12:43 -05:00
1b9d9424b4 fix: clarify interactive mode detection in package installation script 2025-08-05 00:07:06 -05:00
ac89fa5ec8 feat: add alias for installing packages using custom script 2025-08-05 00:05:14 -05:00
3ba83b8dd1 fix: adjust package selection for non-interactive installations to only include base group 2025-08-05 00:05:05 -05:00
e5202c601f fix: enhance package installation prompt for better user interaction 2025-08-05 00:00:50 -05:00
f634c3a9d1 fix: remove redundant 'def' keyword from function declaration 2025-08-04 23:59:15 -05:00
3187e3e396 fix: update module script check to verify existence only 2025-08-04 23:56:11 -05:00
6883f67b74 fix: ensure utilities are sourced before execution 2025-08-04 23:55:06 -05:00
991a82b833 fix: remove unnecessary line from package installation script 2025-08-04 23:54:21 -05:00
431dffb1a6 refactor: modularize package installation into separate script 2025-08-04 23:53:50 -05:00
96481590bc change: disable branch selection 2025-08-04 23:36:54 -05:00
f3496206eb fix: execute setup scripts in separate shells for modularity 2025-08-04 23:35:48 -05:00
76abbd7d1d fix: simplify script count increment syntax for clarity 2025-08-04 23:29:08 -05:00
980fa75310 fix: script discovery in setup.sh 2025-08-04 23:28:49 -05:00
c76ed8e31e fix: remove unnecessary chmod command for setup.sh execution 2025-08-04 22:06:41 -05:00
3ba293692e Merge feature/stow-restructure into main - complete dotfiles restructure with stow organization 2025-08-04 22:03:03 -05:00
4b50becf6f fix: correct script path in setup.sh for proper execution 2025-08-04 21:03:46 -05:00
a245bebffb fix: ensure setup scripts are executed with bash for compatibility 2025-08-04 20:59:04 -05:00
b08c9acb29 fix: update setup script execution method for improved compatibility 2025-08-04 20:57:20 -05:00
2ef00a0dd9 fix: enhance git pull handling in bootstrap script to manage local changes 2025-08-04 20:57:20 -05:00
c9ccdf3086 fix: update setup script execution method for improved compatibility 2025-08-04 20:53:09 -05:00
ebfb4b965d fix: enhance git pull handling in bootstrap script to manage local changes 2025-08-04 20:51:13 -05:00
67504dccff remove: standalone branch selection script - functionality moved to bootstrap.sh 2025-08-04 20:44:53 -05:00
73a38cfa55 remove: standalone branch selection script - functionality moved to bootstrap.sh 2025-08-04 20:44:25 -05:00
c366cd7789 feat: restructure dotfiles setup with bootstrap and setup scripts, enhance branch selection and backup processes 2025-08-04 20:42:46 -05:00
f824ca52d3 feat: restructure dotfiles setup with bootstrap and setup scripts, enhance branch selection and backup processes 2025-08-04 20:39:16 -05:00
f9ff648977 Merge branch 'main' into feature/stow-restructure 2025-08-04 20:00:32 -05:00
03150e539d feat: implement modular numbered script system
- Add scripts/ directory with numbered execution pattern
- Create 00-check-dependencies.sh for requirement validation
- Create 10-backup-files.sh for conflict handling
- Create 20-setup-stow.sh for symlink creation
- Create 90-post-install.sh for cleanup tasks
- Add scripts/utils.sh with colored logging functions
- Update install.sh to auto-discover and execute numbered scripts
- Remove hardcoded logic in favor of flexible script discovery

Benefits:
- Clean separation of concerns (each script has one job)
- Numbered execution order (SSH/systemd convention)
- Colored output for better UX
- Easy to extend with additional scripts
- Same entry point for all branches
2025-08-04 19:50:21 -05:00
c11d846402 feat: enhance branch selection in install script with user prompts and default handling 2025-08-04 19:19:40 -05:00
66cbb97f91 feat: restructure dotfiles with GNU Stow organization
- Reorganize dotfiles into stow/ subdirectories (bash/, git/)
- Add package management system with categorized package lists
- Enhance install.sh with interactive package selection
- Fix package selection logic and improve error handling
- Update dotpull alias to work with new stow structure
- Add comprehensive documentation in README.md

Breaking Changes:
- Dotfiles moved from root to stow/ subdirectories
- Install script now requires user interaction for package selection
- Stow commands now target individual config directories
2025-08-04 19:16:02 -05:00
7fd2fd7317 fix: remove deprecated raminfo alias from .bash_aliases 2025-08-03 17:32:45 -05:00
087ffacfbd fix: correct dotpull alias and improve stow installation script for better directory handling 2025-08-03 17:30:01 -05:00
7bb8a3077c fix: improve dotpull alias and enhance stow installation script with better error handling 2025-08-03 17:12:47 -05:00
4cc4f2347e Merge branch 'main' of ssh://gitea.purpleraft.com:2001/ryan/dotfiles 2025-08-03 17:04:41 -05:00
ebdd73bb31 fix: enhance dotpull alias and improve stow installation script 2025-08-02 13:21:01 -05:00
24 changed files with 1351 additions and 88 deletions

View File

@@ -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:'"

View File

@@ -1,9 +0,0 @@
[user]
name = Ryan Hamilton
email = ryanehamil@outlook.com
[color]
ui = auto
[core]
editor = nano

View File

@@ -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
View 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
View 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

View File

@@ -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
View 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
View 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
View 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
View File

@@ -0,0 +1,6 @@
# GUI Applications
# Desktop applications (for systems with GUI)
fonts-firacode
code
vlc
terminator

159
packages/install.sh Normal file
View 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"

View 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

View 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
View 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

View 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

View 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

View 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
View 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
View 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
View 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
}

View File

@@ -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
View 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
View 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