334 lines
No EOL
7.9 KiB
Bash
Executable file
334 lines
No EOL
7.9 KiB
Bash
Executable file
#!/bin/bash
|
|
|
|
# forge - A home-manager like script for Fedora
|
|
# Provides dotfile management and package installation functionality
|
|
|
|
set -euo pipefail
|
|
|
|
# Configuration
|
|
FORGE_DIR="${FORGE_DIR:-$HOME/.config/forge}"
|
|
FORGE_TOML="$FORGE_DIR/forge.toml"
|
|
|
|
# Colors
|
|
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}[INFO]${NC} $1"
|
|
}
|
|
|
|
log_success() {
|
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
|
}
|
|
|
|
log_warning() {
|
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
|
}
|
|
|
|
log_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1"
|
|
}
|
|
|
|
# Initialize command - creates forge.toml and .config directory
|
|
init() {
|
|
log_info "Initializing forge..."
|
|
|
|
# Create directory
|
|
mkdir -p "$FORGE_DIR"
|
|
|
|
# Create initial forge.toml if it doesn't exist
|
|
if [[ ! -f "$FORGE_TOML" ]]; then
|
|
cat >"$FORGE_TOML" <<'EOF'
|
|
# Forge Configuration File
|
|
# User-level configuration similar to home-manager/nixos
|
|
|
|
[forge]
|
|
version = "1.0"
|
|
last_switch = null
|
|
|
|
[packages]
|
|
# List of packages to install using dnf
|
|
# Examples:
|
|
# git = true
|
|
# vim = true
|
|
# curl = true
|
|
|
|
[dotfiles]
|
|
# Dotfiles to manage with symlinks
|
|
# Format: "target_path" = "source_path"
|
|
# target_path: where the symlink should be created (relative to home directory)
|
|
# source_path: where the actual file is stored (relative to forge directory)
|
|
# Examples:
|
|
# ".bashrc" = "dotfiles/.bashrc"
|
|
# ".config/vimrc" = "dotfiles/vimrc"
|
|
|
|
[options]
|
|
# Additional options
|
|
backup = true
|
|
backup_dir = "backup"
|
|
EOF
|
|
log_success "Created forge.toml: $FORGE_TOML"
|
|
fi
|
|
|
|
log_success "Initialization complete!"
|
|
log_info "Edit $FORGE_TOML to configure packages and dotfiles"
|
|
log_info "Run 'forge switch' to apply your configuration"
|
|
}
|
|
|
|
# Parse TOML file (basic implementation)
|
|
parse_toml() {
|
|
local section=""
|
|
while IFS= read -r line; do
|
|
# Skip comments and empty lines
|
|
[[ -z "$line" || "$line" == \#* ]] && continue
|
|
|
|
# Check for section headers
|
|
if [[ "$line" =~ ^\[(.+)\]$ ]]; then
|
|
section="${BASH_REMATCH[1]}"
|
|
continue
|
|
fi
|
|
|
|
# Parse key-value pairs
|
|
if [[ "$line" =~ ^([^=]+)=(.+)$ ]]; then
|
|
local key="${BASH_REMATCH[1]// /}"
|
|
local value="${BASH_REMATCH[2]// /}"
|
|
echo "$section:$key=$value"
|
|
fi
|
|
done <"$FORGE_TOML"
|
|
}
|
|
|
|
# Install packages from forge.toml
|
|
install_packages() {
|
|
log_info "Installing packages..."
|
|
|
|
local packages_installed=false
|
|
|
|
while IFS='=' read -r entry; do
|
|
local section="${entry%%:*}"
|
|
local key_value="${entry#*:}"
|
|
local package="${key_value%%=*}"
|
|
local enabled="${key_value##*=}"
|
|
|
|
if [[ "$section" == "packages" && "$enabled" == "true" ]]; then
|
|
packages_installed=true
|
|
log_info "Installing package: $package"
|
|
if sudo dnf install -y "$package"; then
|
|
log_success "Installed $package"
|
|
else
|
|
log_error "Failed to install $package"
|
|
fi
|
|
fi
|
|
done < <(parse_toml)
|
|
|
|
if [[ "$packages_installed" == false ]]; then
|
|
log_info "No packages to install"
|
|
fi
|
|
}
|
|
|
|
# Deploy dotfiles using symlinks
|
|
deploy_dotfiles() {
|
|
log_info "Deploying dotfiles..."
|
|
|
|
local dotfiles_deployed=false
|
|
|
|
while IFS='=' read -r entry; do
|
|
local section="${entry%%:*}"
|
|
local key_value="${entry#*:}"
|
|
local target="${key_value%%=*}"
|
|
local source="${key_value##*=}"
|
|
|
|
if [[ "$section" == "dotfiles" ]]; then
|
|
dotfiles_deployed=true
|
|
|
|
local target_path="$HOME/$target"
|
|
local source_path="$FORGE_DIR/$source"
|
|
|
|
# Create source directory if it doesn't exist
|
|
local source_dir=$(dirname "$source_path")
|
|
mkdir -p "$source_dir"
|
|
|
|
# Create target directory if it doesn't exist
|
|
local target_dir=$(dirname "$target_path")
|
|
mkdir -p "$target_dir"
|
|
|
|
# Backup existing file if it exists and is not a symlink
|
|
if [[ -f "$target_path" && ! -L "$target_path" ]]; then
|
|
local backup_dir="$FORGE_DIR/backup"
|
|
mkdir -p "$backup_dir"
|
|
local backup_path="$backup_dir/$(basename "$target").$(date +%Y%m%d_%H%M%S).bak"
|
|
cp "$target_path" "$backup_path"
|
|
log_info "Backed up $target_path to $backup_path"
|
|
fi
|
|
|
|
# Remove existing file/symlink
|
|
rm -f "$target_path"
|
|
|
|
# Create symlink
|
|
if ln -s "$source_path" "$target_path"; then
|
|
log_success "Created symlink: $target_path -> $source_path"
|
|
else
|
|
log_error "Failed to create symlink: $target_path -> $source_path"
|
|
fi
|
|
fi
|
|
done < <(parse_toml)
|
|
|
|
if [[ "$dotfiles_deployed" == false ]]; then
|
|
log_info "No dotfiles to deploy"
|
|
fi
|
|
}
|
|
|
|
# Switch command - apply configuration
|
|
switch() {
|
|
log_info "Starting forge switch..."
|
|
|
|
# Check if forge.toml exists
|
|
if [[ ! -f "$FORGE_TOML" ]]; then
|
|
log_error "forge.toml not found. Run 'forge init' first."
|
|
exit 1
|
|
fi
|
|
|
|
# Install packages
|
|
install_packages
|
|
|
|
# Deploy dotfiles
|
|
deploy_dotfiles
|
|
|
|
# Update last_switch timestamp
|
|
if command -v sed >/dev/null 2>&1; then
|
|
sed -i "s/last_switch = .*/last_switch = \"$(date -Iseconds)\"/" "$FORGE_TOML"
|
|
fi
|
|
|
|
log_success "Switch completed successfully!"
|
|
}
|
|
|
|
# Status command - show current configuration
|
|
status() {
|
|
log_info "Forge Status"
|
|
echo " Config directory: $FORGE_DIR"
|
|
echo " Config file: $FORGE_TOML"
|
|
|
|
if [[ -f "$FORGE_TOML" ]]; then
|
|
echo
|
|
log_info "Configuration:"
|
|
|
|
# Show last switch time
|
|
local last_switch=$(grep "last_switch" "$FORGE_TOML" | cut -d'=' -f2 | tr -d ' "')
|
|
if [[ -n "$last_switch" && "$last_switch" != "null" ]]; then
|
|
echo " Last switch: $last_switch"
|
|
else
|
|
echo " Last switch: Never"
|
|
fi
|
|
|
|
echo
|
|
log_info "Packages:"
|
|
local package_count=0
|
|
while IFS='=' read -r entry; do
|
|
local section="${entry%%:*}"
|
|
local key_value="${entry#*:}"
|
|
local package="${key_value%%=*}"
|
|
local enabled="${key_value##*=}"
|
|
|
|
if [[ "$section" == "packages" && "$enabled" == "true" ]]; then
|
|
echo " $package"
|
|
((package_count++))
|
|
fi
|
|
done < <(parse_toml)
|
|
|
|
if [[ $package_count -eq 0 ]]; then
|
|
echo " No packages configured"
|
|
else
|
|
echo " Total: $package_count packages"
|
|
fi
|
|
|
|
echo
|
|
log_info "Dotfiles:"
|
|
local dotfile_count=0
|
|
while IFS='=' read -r entry; do
|
|
local section="${entry%%:*}"
|
|
local key_value="${entry#*:}"
|
|
local target="${key_value%%=*}"
|
|
local source="${key_value##*=}"
|
|
|
|
if [[ "$section" == "dotfiles" ]]; then
|
|
echo " $target -> $source"
|
|
((dotfile_count++))
|
|
fi
|
|
done < <(parse_toml)
|
|
|
|
if [[ $dotfile_count -eq 0 ]]; then
|
|
echo " No dotfiles configured"
|
|
else
|
|
echo " Total: $dotfile_count dotfiles"
|
|
fi
|
|
else
|
|
echo " No configuration file found"
|
|
fi
|
|
}
|
|
|
|
# Show help
|
|
show_help() {
|
|
cat <<EOF
|
|
forge - A home-manager like script for Fedora
|
|
|
|
USAGE:
|
|
forge <COMMAND>
|
|
|
|
COMMANDS:
|
|
init Initialize configuration structure (creates forge.toml)
|
|
switch Apply configuration (install packages, deploy dotfiles)
|
|
status Show current configuration status
|
|
help Show this help message
|
|
|
|
FEATURES:
|
|
- TOML-based configuration in forge.toml
|
|
- Package management via dnf
|
|
- Dotfile management via symlinks
|
|
- Automatic backup of existing files
|
|
- User-level configuration like home-manager/nixos
|
|
|
|
FILES:
|
|
$FORGE_DIR/ Main configuration directory
|
|
$FORGE_TOML Configuration file for packages and dotfiles
|
|
|
|
EXAMPLES:
|
|
forge init
|
|
# Edit $FORGE_TOML to add packages and dotfiles
|
|
forge switch
|
|
forge status
|
|
|
|
EOF
|
|
}
|
|
|
|
# Main script logic
|
|
main() {
|
|
case "${1:-}" in
|
|
"init")
|
|
init
|
|
;;
|
|
"switch")
|
|
switch
|
|
;;
|
|
"status")
|
|
status
|
|
;;
|
|
"help" | "--help" | "-h")
|
|
show_help
|
|
;;
|
|
"")
|
|
log_error "No command specified. Use 'help' for usage information."
|
|
exit 1
|
|
;;
|
|
*)
|
|
log_error "Unknown command: $1"
|
|
show_help
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Run main function with all arguments
|
|
main "$@" |