reworked
This commit is contained in:
parent
0cf22d6e5e
commit
980b1c01a8
2 changed files with 336 additions and 524 deletions
289
README.md
289
README.md
|
|
@ -1,14 +1,15 @@
|
|||
# fedora-home-manager
|
||||
# Forge - A Home-Manager Like Tool for Fedora
|
||||
|
||||
A home-manager like script for Fedora that provides dotfile management and package installation functionality.
|
||||
A lightweight configuration management tool for Fedora that provides dotfile management and package installation functionality through a centralized TOML configuration.
|
||||
|
||||
## Overview
|
||||
|
||||
fedora-home-manager is a bash script that helps you manage your Fedora system configuration by:
|
||||
Forge is a bash script that helps you manage your Fedora system configuration by:
|
||||
- Installing and managing system packages via dnf
|
||||
- Deploying and managing dotfiles to your home directory
|
||||
- Automatically cleaning up packages that are no longer needed
|
||||
- Tracking state of managed files and packages
|
||||
- Managing dotfiles through symlinks to actual files
|
||||
- Centralized configuration via a single `forge.toml` file
|
||||
- User-level configuration similar to home-manager/nixos
|
||||
- Automatic backup of existing files before replacement
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
@ -19,7 +20,7 @@ fedora-home-manager is a bash script that helps you manage your Fedora system co
|
|||
```
|
||||
3. Optionally, move it to a directory in your PATH:
|
||||
```bash
|
||||
sudo mv forge /usr/local/bin/fedora-home-manager
|
||||
sudo mv forge /usr/local/bin/forge
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
|
@ -28,88 +29,44 @@ fedora-home-manager is a bash script that helps you manage your Fedora system co
|
|||
# Initialize the configuration structure
|
||||
./forge init
|
||||
|
||||
# Add a dotfile to management
|
||||
./forge add ~/.bashrc
|
||||
|
||||
# Add packages to install (edit the packages file)
|
||||
echo "git = \"latest\"" >> ~/.config/fedora-home-manager/packages.toml
|
||||
echo "vim = \"latest\"" >> ~/.config/fedora-home-manager/packages.toml
|
||||
# Edit the configuration file to add packages and dotfiles
|
||||
nano ~/.config/forge/forge.toml
|
||||
|
||||
# Apply configuration
|
||||
./forge switch
|
||||
|
||||
# Check status
|
||||
./forge status
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
### `init`
|
||||
Initialize the configuration structure and create example files.
|
||||
Initialize the configuration structure and create `forge.toml`.
|
||||
|
||||
```bash
|
||||
./forge init
|
||||
```
|
||||
|
||||
Creates:
|
||||
- `~/.config/fedora-home-manager/` - Main configuration directory
|
||||
- `~/.config/fedora-home-manager/dotfiles/` - Directory for your dotfiles
|
||||
- `~/.config/fedora-home-manager/packages.toml` - TOML file with packages to install
|
||||
- `~/.config/fedora-home-manager/state.json` - State tracking file
|
||||
- `~/.config/fedora-home-manager/backup/` - Backup directory for existing files
|
||||
- `~/.config/forge/` - Main configuration directory
|
||||
- `~/.config/forge/forge.toml` - Central configuration file
|
||||
|
||||
### `switch`
|
||||
Apply the current configuration (install packages, deploy dotfiles, cleanup old packages).
|
||||
Apply the current configuration (install packages, deploy dotfiles).
|
||||
|
||||
```bash
|
||||
./forge switch
|
||||
```
|
||||
|
||||
This command:
|
||||
1. Installs all packages listed in `packages.toml`
|
||||
2. Uninstalls packages that are no longer in `packages.toml`
|
||||
3. Deploys all dotfiles from the dotfiles directory to your home
|
||||
4. Creates backups of existing files before overwriting
|
||||
5. Updates the state tracking
|
||||
|
||||
### `build`
|
||||
Alias for `switch` command.
|
||||
|
||||
```bash
|
||||
./forge build
|
||||
```
|
||||
|
||||
### `add <file>`
|
||||
Add a file to dotfiles management.
|
||||
|
||||
```bash
|
||||
./forge add ~/.bashrc
|
||||
./forge add ~/.vimrc
|
||||
./forge add ~/.config/alacritty/alacritty.yml
|
||||
```
|
||||
|
||||
The file is copied to the dotfiles directory while maintaining its relative path structure.
|
||||
|
||||
### `remove <path>`
|
||||
Remove a dotfile from management.
|
||||
|
||||
```bash
|
||||
./forge remove .bashrc
|
||||
./forge remove .config/alacritty/alacritty.yml
|
||||
```
|
||||
|
||||
Optionally restores the original file from backup.
|
||||
|
||||
### `list`
|
||||
List all managed files and packages.
|
||||
|
||||
```bash
|
||||
./forge list
|
||||
```
|
||||
|
||||
Shows:
|
||||
- All dotfiles currently under management
|
||||
- All packages that will be installed
|
||||
1. Installs all packages listed in `forge.toml`
|
||||
2. Creates symlinks for all configured dotfiles
|
||||
3. Creates backups of existing files before replacing them
|
||||
4. Updates the last switch timestamp
|
||||
|
||||
### `status`
|
||||
Show current status and configuration information.
|
||||
Show current configuration status and information.
|
||||
|
||||
```bash
|
||||
./forge status
|
||||
|
|
@ -118,7 +75,7 @@ Show current status and configuration information.
|
|||
Displays:
|
||||
- Configuration directory paths
|
||||
- Last switch timestamp
|
||||
- List of managed files and packages
|
||||
- List of configured packages and dotfiles
|
||||
|
||||
### `help`
|
||||
Show help message with all available commands.
|
||||
|
|
@ -127,122 +84,168 @@ Show help message with all available commands.
|
|||
./forge help
|
||||
```
|
||||
|
||||
## Configuration Files
|
||||
## Configuration
|
||||
|
||||
### forge.toml
|
||||
The central configuration file located at `~/.config/forge/forge.toml`:
|
||||
|
||||
### packages.toml
|
||||
List of Fedora packages to install in TOML format:
|
||||
```toml
|
||||
# Fedora packages configuration
|
||||
# TOML format for better package management
|
||||
# Forge Configuration File
|
||||
# User-level configuration similar to home-manager/nixos
|
||||
|
||||
[forge]
|
||||
version = "1.0"
|
||||
last_switch = null
|
||||
|
||||
[packages]
|
||||
# Core development tools
|
||||
git = "latest"
|
||||
vim = "latest"
|
||||
curl = "latest"
|
||||
wget = "latest"
|
||||
# List of packages to install using dnf
|
||||
# Set to true to install, false or comment out to skip
|
||||
git = true
|
||||
vim = true
|
||||
curl = true
|
||||
wget = true
|
||||
|
||||
# Additional packages can be added with version specifications
|
||||
# Example: package_name = "version" or "latest"
|
||||
[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)
|
||||
".bashrc" = "dotfiles/.bashrc"
|
||||
".config/vimrc" = "dotfiles/vimrc"
|
||||
".config/alacritty/alacritty.yml" = "dotfiles/alacritty.yml"
|
||||
|
||||
[options]
|
||||
# Additional options
|
||||
backup = true
|
||||
backup_dir = "backup"
|
||||
```
|
||||
|
||||
### dotfiles directory
|
||||
Place your dotfiles in `~/.config/fedora-home-manager/dotfiles/` maintaining the same directory structure as in your home:
|
||||
### Configuration Sections
|
||||
|
||||
#### `[forge]`
|
||||
- `version`: Configuration file version
|
||||
- `last_switch`: Timestamp of last switch operation (auto-updated)
|
||||
|
||||
#### `[packages]`
|
||||
List of packages to install via dnf. Set to `true` to install, `false` or comment out to skip.
|
||||
|
||||
#### `[dotfiles]`
|
||||
Dotfile mappings using symlinks:
|
||||
- **Key**: Target path where symlink should be created (relative to home directory)
|
||||
- **Value**: Source path where actual file is stored (relative to forge directory)
|
||||
|
||||
#### `[options]`
|
||||
Additional configuration options:
|
||||
- `backup`: Enable/disable backup of existing files (default: true)
|
||||
- `backup_dir`: Directory name for backups (relative to forge directory)
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
~/.config/fedora-home-manager/dotfiles/
|
||||
├── .bashrc
|
||||
├── .vimrc
|
||||
└── .config/
|
||||
└── alacritty/
|
||||
└── alacritty.yml
|
||||
~/.config/forge/
|
||||
├── forge.toml # Main configuration file
|
||||
├── dotfiles/ # Your actual dotfiles (source files)
|
||||
│ ├── .bashrc
|
||||
│ ├── vimrc
|
||||
│ └── alacritty.yml
|
||||
└── backup/ # Backups of replaced files
|
||||
├── .bashrc.20231121_143022.bak
|
||||
└── vimrc.20231121_143022.bak
|
||||
```
|
||||
|
||||
### state.json
|
||||
Internal state tracking file (managed automatically):
|
||||
- Tracks installed packages for cleanup
|
||||
- Records last switch timestamp
|
||||
- Maintains list of managed files
|
||||
## How It Works
|
||||
|
||||
## Features
|
||||
### Dotfile Management
|
||||
Forge uses symlinks to manage dotfiles:
|
||||
1. Your actual dotfiles are stored in `~/.config/forge/dotfiles/`
|
||||
2. Symlinks are created from your home directory to these files
|
||||
3. This allows you to version control your dotfiles in one place
|
||||
4. Changes to the source files are immediately reflected in your home directory
|
||||
|
||||
### Automatic Package Cleanup
|
||||
When you remove a package from `packages.toml` and run `switch`, the package will be automatically uninstalled from your system.
|
||||
|
||||
### Package State Tracking
|
||||
Previously installed packages are tracked to enable cleanup operations.
|
||||
|
||||
### TOML Format Support
|
||||
Packages are managed in TOML format for better organization and version support.
|
||||
### Package Management
|
||||
Packages are managed through the `[packages]` section in `forge.toml`:
|
||||
- Packages set to `true` are installed via `dnf`
|
||||
- Packages set to `false` or commented out are ignored
|
||||
- No automatic uninstallation (unlike the previous version)
|
||||
|
||||
### Backup System
|
||||
Before overwriting existing files, the script creates timestamped backups in the backup directory.
|
||||
|
||||
### State Tracking
|
||||
Previously installed packages are tracked to enable intelligent cleanup operations.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
- `HOME_MANAGER_DIR`: Override the default configuration directory (default: `~/.config/fedora-home-manager`)
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `dnf` - Fedora package manager
|
||||
- `jq` - JSON processor (optional, for enhanced state tracking)
|
||||
Before creating symlinks, Forge:
|
||||
1. Checks if the target file exists and is not a symlink
|
||||
2. Creates a timestamped backup in the backup directory
|
||||
3. Removes the original file
|
||||
4. Creates the symlink to your managed dotfile
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Setup
|
||||
```bash
|
||||
# Initialize
|
||||
# Initialize forge
|
||||
./forge init
|
||||
|
||||
# Add your shell configuration
|
||||
./forge add ~/.bashrc
|
||||
./forge add ~/.bash_profile
|
||||
# Create your dotfiles directory and add files
|
||||
mkdir -p ~/.config/forge/dotfiles
|
||||
echo "export EDITOR=vim" > ~/.config/forge/dotfiles/.bashrc
|
||||
|
||||
# Add development tools
|
||||
cat >> ~/.config/fedora-home-manager/packages.toml << 'EOF'
|
||||
git = "latest"
|
||||
vim = "latest"
|
||||
nodejs = "latest"
|
||||
npm = "latest"
|
||||
EOF
|
||||
# Edit forge.toml to configure
|
||||
nano ~/.config/forge/forge.toml
|
||||
|
||||
# Add to [packages] section:
|
||||
git = true
|
||||
vim = true
|
||||
|
||||
# Add to [dotfiles] section:
|
||||
".bashrc" = "dotfiles/.bashrc"
|
||||
|
||||
# Apply configuration
|
||||
./forge switch
|
||||
```
|
||||
|
||||
### Managing Configuration Files
|
||||
### Managing Application Configurations
|
||||
```bash
|
||||
# Add application configurations
|
||||
./forge add ~/.config/alacritty/alacritty.yml
|
||||
./forge add ~/.config/git/config
|
||||
./forge add ~/.config/nvim/init.vim
|
||||
# Add alacritty configuration
|
||||
mkdir -p ~/.config/forge/dotfiles/.config/alacritty
|
||||
cp ~/.config/alacritty/alacritty.yml ~/.config/forge/dotfiles/.config/alacritty/
|
||||
|
||||
# Check what's managed
|
||||
./forge list
|
||||
# Edit forge.toml
|
||||
nano ~/.config/forge/forge.toml
|
||||
|
||||
# Add to [dotfiles] section:
|
||||
".config/alacritty/alacritty.yml" = "dotfiles/.config/alacritty/alacritty.yml"
|
||||
|
||||
# Apply changes
|
||||
./forge switch
|
||||
```
|
||||
|
||||
### Removing Packages
|
||||
### Version Control Your Configuration
|
||||
```bash
|
||||
# Edit packages.toml and remove entries for packages you don't want
|
||||
# Then run switch to uninstall them
|
||||
./forge switch
|
||||
# Initialize git repository in forge directory
|
||||
cd ~/.config/forge
|
||||
git init
|
||||
git add .
|
||||
git commit -m "Initial configuration"
|
||||
|
||||
# Now you can version control your entire system configuration
|
||||
git add forge.toml dotfiles/
|
||||
git commit -m "Updated vim configuration"
|
||||
```
|
||||
|
||||
## File Structure
|
||||
## Environment Variables
|
||||
|
||||
```
|
||||
~/.config/fedora-home-manager/
|
||||
├── dotfiles/ # Your dotfiles to deploy
|
||||
├── packages.toml # TOML file with packages to install
|
||||
├── state.json # State tracking
|
||||
└── backup/ # Backups of replaced files
|
||||
```
|
||||
- `FORGE_DIR`: Override the default configuration directory (default: `~/.config/forge`)
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `dnf` - Fedora package manager
|
||||
- `sed` - For updating configuration file (usually pre-installed)
|
||||
|
||||
## Migration from Previous Version
|
||||
|
||||
If you were using the old version of forge:
|
||||
|
||||
1. Your existing configuration will not be automatically migrated
|
||||
2. Run `./forge init` to create the new `forge.toml` structure
|
||||
3. Manually migrate your packages and dotfile configurations to the new TOML format
|
||||
4. Move your existing dotfiles from the old directory to `~/.config/forge/dotfiles/`
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
537
forge
537
forge
|
|
@ -7,10 +7,7 @@ set -euo pipefail
|
|||
|
||||
# Configuration
|
||||
FORGE_DIR="${FORGE_DIR:-$HOME/.config/forge}"
|
||||
DOTFILES_DIR="$FORGE_DIR/dotfiles"
|
||||
PACKAGES_FILE="$FORGE_DIR/packages.toml"
|
||||
STATE_FILE="$FORGE_DIR/state.json"
|
||||
BACKUP_DIR="$FORGE_DIR/backup"
|
||||
FORGE_TOML="$FORGE_DIR/forge.toml"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
|
|
@ -36,114 +33,88 @@ log_error() {
|
|||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Initialize command - set up the configuration structure
|
||||
# Initialize command - creates forge.toml and .config directory
|
||||
init() {
|
||||
log_info "Initializing forge..."
|
||||
|
||||
# Create directories
|
||||
# Create directory
|
||||
mkdir -p "$FORGE_DIR"
|
||||
mkdir -p "$DOTFILES_DIR"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
# Create initial state file if it doesn't exist
|
||||
if [[ ! -f "$STATE_FILE" ]]; then
|
||||
echo '{"version": "1.0", "last_switch": null, "managed_files": []}' >"$STATE_FILE"
|
||||
log_success "Created state file: $STATE_FILE"
|
||||
fi
|
||||
# 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
|
||||
|
||||
# Create initial packages file with simplified format if it doesn't exist
|
||||
if [[ ! -f "$PACKAGES_FILE" ]]; then
|
||||
cat >"$PACKAGES_FILE" <<'EOF'
|
||||
# Forge packages configuration
|
||||
# Simple format: just list package names, one per line
|
||||
[forge]
|
||||
version = "1.0"
|
||||
last_switch = null
|
||||
|
||||
# Core development tools
|
||||
# git
|
||||
# vim
|
||||
# curl
|
||||
# wget
|
||||
[packages]
|
||||
# List of packages to install using dnf
|
||||
# Examples:
|
||||
# git = true
|
||||
# vim = true
|
||||
# curl = true
|
||||
|
||||
# Additional packages can be added as simple package names
|
||||
# Example: package_name
|
||||
[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 packages file: $PACKAGES_FILE"
|
||||
fi
|
||||
|
||||
# Create example dotfiles structure
|
||||
if [[ ! -f "$DOTFILES_DIR/.gitkeep" ]]; then
|
||||
touch "$DOTFILES_DIR/.gitkeep"
|
||||
log_info "Created dotfiles directory: $DOTFILES_DIR"
|
||||
log_success "Created forge.toml: $FORGE_TOML"
|
||||
fi
|
||||
|
||||
log_success "Initialization complete!"
|
||||
log_info "Add packages to $PACKAGES_FILE and dotfiles to $DOTFILES_DIR/"
|
||||
log_info "Edit $FORGE_TOML to configure packages and dotfiles"
|
||||
log_info "Run 'forge switch' to apply your configuration"
|
||||
}
|
||||
|
||||
# Initialize directories
|
||||
init_dirs() {
|
||||
mkdir -p "$FORGE_DIR"
|
||||
mkdir -p "$DOTFILES_DIR"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
# Create initial state file if it doesn't exist
|
||||
if [[ ! -f "$STATE_FILE" ]]; then
|
||||
echo '{"version": "1.0", "last_switch": null, "managed_files": []}' >"$STATE_FILE"
|
||||
fi
|
||||
|
||||
# Create initial packages file if it doesn't exist
|
||||
if [[ ! -f "$PACKAGES_FILE" ]]; then
|
||||
touch "$PACKAGES_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# Backup existing files
|
||||
backup_file() {
|
||||
local file="$1"
|
||||
local backup_path="$BACKUP_DIR/$(basename "$file").$(date +%Y%m%d_%H%M%S).bak"
|
||||
|
||||
if [[ -f "$file" ]]; then
|
||||
cp "$file" "$backup_path"
|
||||
log_info "Backed up $file to $backup_path"
|
||||
fi
|
||||
}
|
||||
|
||||
# Restore files from backup
|
||||
restore_backup() {
|
||||
local file="$1"
|
||||
local latest_backup=$(ls -t "$BACKUP_DIR"/$(basename "$file")*.bak 2>/dev/null | head -1)
|
||||
|
||||
if [[ -n "$latest_backup" ]]; then
|
||||
cp "$latest_backup" "$file"
|
||||
log_success "Restored $file from backup"
|
||||
return 0
|
||||
else
|
||||
log_warning "No backup found for $file"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Install packages using dnf
|
||||
install_packages() {
|
||||
if [[ ! -f "$PACKAGES_FILE" ]] || [[ ! -s "$PACKAGES_FILE" ]]; then
|
||||
log_info "No packages to install"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Installing packages..."
|
||||
|
||||
local current_packages=()
|
||||
|
||||
# Parse simple format and extract package names
|
||||
# Parse TOML file (basic implementation)
|
||||
parse_toml() {
|
||||
local section=""
|
||||
while IFS= read -r line; do
|
||||
# Skip empty lines and comments
|
||||
# Skip comments and empty lines
|
||||
[[ -z "$line" || "$line" == \#* ]] && continue
|
||||
|
||||
# Remove whitespace and use as package name
|
||||
local package="${line// /}" # Remove whitespace
|
||||
if [[ -n "$package" ]]; then
|
||||
current_packages+=("$package")
|
||||
# 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"
|
||||
|
|
@ -151,143 +122,74 @@ install_packages() {
|
|||
log_error "Failed to install $package"
|
||||
fi
|
||||
fi
|
||||
done <"$PACKAGES_FILE"
|
||||
done < <(parse_toml)
|
||||
|
||||
# Update state with current packages
|
||||
update_installed_packages "${current_packages[@]}"
|
||||
}
|
||||
|
||||
# Uninstall packages that are no longer in packages.toml
|
||||
cleanup_packages() {
|
||||
log_info "Cleaning up packages that are no longer needed..."
|
||||
|
||||
# Get current packages from packages.toml
|
||||
local current_packages=()
|
||||
if [[ -f "$PACKAGES_FILE" ]]; then
|
||||
while IFS= read -r line; do
|
||||
# Skip empty lines and comments
|
||||
[[ -z "$line" || "$line" == \#* ]] && continue
|
||||
|
||||
# Remove whitespace and use as package name
|
||||
local package="${line// /}" # Remove whitespace
|
||||
if [[ -n "$package" ]]; then
|
||||
current_packages+=("$package")
|
||||
fi
|
||||
done <"$PACKAGES_FILE"
|
||||
fi
|
||||
|
||||
# Get previously installed packages from state
|
||||
local previous_packages=()
|
||||
while IFS= read -r package; do
|
||||
[[ -n "$package" ]] && previous_packages+=("$package")
|
||||
done < <(get_installed_packages)
|
||||
|
||||
# Find packages to uninstall (in previous but not in current)
|
||||
local packages_to_uninstall=()
|
||||
for prev_pkg in "${previous_packages[@]}"; do
|
||||
local found=false
|
||||
for curr_pkg in "${current_packages[@]}"; do
|
||||
if [[ "$prev_pkg" == "$curr_pkg" ]]; then
|
||||
found=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ "$found" == false ]]; then
|
||||
packages_to_uninstall+=("$prev_pkg")
|
||||
fi
|
||||
done
|
||||
|
||||
# Uninstall packages that are no longer needed
|
||||
if [[ ${#packages_to_uninstall[@]} -gt 0 ]]; then
|
||||
log_info "Uninstalling ${#packages_to_uninstall[@]} packages that are no longer needed..."
|
||||
for package in "${packages_to_uninstall[@]}"; do
|
||||
log_info "Uninstalling package: $package"
|
||||
if sudo dnf remove -y "$package"; then
|
||||
log_success "Uninstalled $package"
|
||||
else
|
||||
log_error "Failed to uninstall $package"
|
||||
fi
|
||||
done
|
||||
else
|
||||
log_info "No packages to uninstall"
|
||||
if [[ "$packages_installed" == false ]]; then
|
||||
log_info "No packages to install"
|
||||
fi
|
||||
}
|
||||
|
||||
# Deploy dotfiles
|
||||
# Deploy dotfiles using symlinks
|
||||
deploy_dotfiles() {
|
||||
if [[ ! -d "$DOTFILES_DIR" ]] || [[ -z "$(ls -A "$DOTFILES_DIR" 2>/dev/null)" ]]; then
|
||||
log_info "No dotfiles to deploy"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Deploying dotfiles..."
|
||||
|
||||
# Find all dotfiles and deploy them
|
||||
find "$DOTFILES_DIR" -type f | while read -r src_file; do
|
||||
# Get relative path from DOTFILES_DIR
|
||||
rel_path="${src_file#$DOTFILES_DIR/}"
|
||||
dest_file="$HOME/$rel_path"
|
||||
local dotfiles_deployed=false
|
||||
|
||||
# Create destination directory if it doesn't exist
|
||||
dest_dir=$(dirname "$dest_file")
|
||||
mkdir -p "$dest_dir"
|
||||
while IFS='=' read -r entry; do
|
||||
local section="${entry%%:*}"
|
||||
local key_value="${entry#*:}"
|
||||
local target="${key_value%%=*}"
|
||||
local source="${key_value##*=}"
|
||||
|
||||
# Backup existing file
|
||||
backup_file "$dest_file"
|
||||
if [[ "$section" == "dotfiles" ]]; then
|
||||
dotfiles_deployed=true
|
||||
|
||||
# Copy file to destination
|
||||
cp "$src_file" "$dest_file"
|
||||
log_success "Deployed $rel_path to $dest_file"
|
||||
local target_path="$HOME/$target"
|
||||
local source_path="$FORGE_DIR/$source"
|
||||
|
||||
# Add to managed files list
|
||||
update_managed_files "$dest_file"
|
||||
done
|
||||
}
|
||||
# Create source directory if it doesn't exist
|
||||
local source_dir=$(dirname "$source_path")
|
||||
mkdir -p "$source_dir"
|
||||
|
||||
# Update managed files list in state
|
||||
update_managed_files() {
|
||||
local file="$1"
|
||||
# Create target directory if it doesn't exist
|
||||
local target_dir=$(dirname "$target_path")
|
||||
mkdir -p "$target_dir"
|
||||
|
||||
# Use jq to update the state file if available, otherwise use a simple approach
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
jq --arg file "$file" '.managed_files += [$file] | .managed_files = unique' "$STATE_FILE" >"$STATE_FILE.tmp" &&
|
||||
mv "$STATE_FILE.tmp" "$STATE_FILE"
|
||||
else
|
||||
# Simple approach without jq
|
||||
log_warning "jq not found, state tracking limited"
|
||||
# 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
|
||||
}
|
||||
|
||||
# Update installed packages list in state
|
||||
update_installed_packages() {
|
||||
local packages=("$@")
|
||||
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
# Create JSON array of packages
|
||||
local packages_json=$(printf '%s\n' "${packages[@]}" | jq -R . | jq -s .)
|
||||
jq --argjson packages "$packages_json" '.installed_packages = $packages' "$STATE_FILE" >"$STATE_FILE.tmp" &&
|
||||
mv "$STATE_FILE.tmp" "$STATE_FILE"
|
||||
else
|
||||
log_warning "jq not found, package tracking limited"
|
||||
fi
|
||||
}
|
||||
|
||||
# Get previously installed packages from state
|
||||
get_installed_packages() {
|
||||
if command -v jq >/dev/null 2>&1 && [[ -f "$STATE_FILE" ]]; then
|
||||
jq -r '.installed_packages[]? // empty' "$STATE_FILE" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# Switch command - main deployment function
|
||||
# Switch command - apply configuration
|
||||
switch() {
|
||||
log_info "Starting forge switch..."
|
||||
|
||||
# Initialize directories
|
||||
init_dirs
|
||||
|
||||
# Clean up packages that are no longer needed
|
||||
cleanup_packages
|
||||
# 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
|
||||
|
|
@ -295,142 +197,76 @@ switch() {
|
|||
# Deploy dotfiles
|
||||
deploy_dotfiles
|
||||
|
||||
# Update state
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
jq --arg timestamp "$(date -Iseconds)" '.last_switch = $timestamp' "$STATE_FILE" >"$STATE_FILE.tmp" &&
|
||||
mv "$STATE_FILE.tmp" "$STATE_FILE"
|
||||
# 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!"
|
||||
}
|
||||
|
||||
# Build command (alias for switch)
|
||||
build() {
|
||||
switch
|
||||
}
|
||||
|
||||
# Add a dotfile to management
|
||||
add_dotfile() {
|
||||
local source_file="$1"
|
||||
|
||||
if [[ ! -f "$source_file" ]]; then
|
||||
log_error "File $source_file does not exist"
|
||||
return 1
|
||||
fi
|
||||
|
||||
init_dirs
|
||||
|
||||
# Get relative path from home directory
|
||||
if [[ "$source_file" == "$HOME"* ]]; then
|
||||
rel_path="${source_file#$HOME/}"
|
||||
else
|
||||
rel_path="$(basename "$source_file")"
|
||||
fi
|
||||
|
||||
dest_file="$DOTFILES_DIR/$rel_path"
|
||||
dest_dir=$(dirname "$dest_file")
|
||||
|
||||
# Create destination directory
|
||||
mkdir -p "$dest_dir"
|
||||
|
||||
# Copy file to dotfiles directory
|
||||
cp "$source_file" "$dest_file"
|
||||
log_success "Added $source_file to dotfiles management"
|
||||
}
|
||||
|
||||
# Remove a dotfile from management
|
||||
remove_dotfile() {
|
||||
local rel_path="$1"
|
||||
local dotfile="$DOTFILES_DIR/$rel_path"
|
||||
local home_file="$HOME/$rel_path"
|
||||
|
||||
if [[ -f "$dotfile" ]]; then
|
||||
rm "$dotfile"
|
||||
log_success "Removed $rel_path from dotfiles management"
|
||||
fi
|
||||
|
||||
# Ask if user wants to restore from backup
|
||||
read -p "Do you want to restore $rel_path from backup? (y/N): " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
restore_backup "$home_file"
|
||||
fi
|
||||
}
|
||||
|
||||
# Generate random Linux-inspired name
|
||||
generate_forge_name() {
|
||||
local adjectives=("swift" "bold" "noble" "fierce" "wise" "ancient" "crystal" "iron" "steel" "shadow" "light" "dark" "golden" "silver" "bronze" "cosmic" "quantum" "neural" "atomic" "stellar")
|
||||
local nouns=("eagle" "wolf" "lion" "tiger" "dragon" "phoenix" "hawk" "falcon" "bear" "panther" "lynx" "owl" "raven" "cobra" "viper" "python" "jaguar" "leopard" "cheetah" "cougar")
|
||||
local suffix=("linux" "kernel" "system" "forge" "core" "engine" "matrix" "network" "protocol" "framework" "platform" "architecture" "interface" "module" "library" "runtime" "daemon" "service" "process" "thread")
|
||||
|
||||
local adj=${adjectives[$RANDOM % ${#adjectives[@]}]}
|
||||
local noun=${nouns[$RANDOM % ${#nouns[@]}]}
|
||||
local suff=${suffix[$RANDOM % ${#suffix[@]}]}
|
||||
|
||||
echo "${adj}-${noun}-${suff}"
|
||||
}
|
||||
|
||||
# List managed files with status
|
||||
list_managed() {
|
||||
local forge_name=$(generate_forge_name)
|
||||
|
||||
log_info "Forge Status: $forge_name"
|
||||
# Status command - show current configuration
|
||||
status() {
|
||||
log_info "Forge Status"
|
||||
echo " Config directory: $FORGE_DIR"
|
||||
echo " Dotfiles directory: $DOTFILES_DIR"
|
||||
echo " Packages file: $PACKAGES_FILE"
|
||||
echo " Config file: $FORGE_TOML"
|
||||
|
||||
if command -v jq >/dev/null 2>&1 && [[ -f "$STATE_FILE" ]]; then
|
||||
last_switch=$(jq -r '.last_switch' "$STATE_FILE")
|
||||
if [[ "$last_switch" != "null" ]]; then
|
||||
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
|
||||
fi
|
||||
|
||||
echo
|
||||
log_info "Managed dotfiles:"
|
||||
if [[ -d "$DOTFILES_DIR" ]]; then
|
||||
local dotfile_count=$(find "$DOTFILES_DIR" -type f | wc -l)
|
||||
if [[ $dotfile_count -gt 0 ]]; then
|
||||
find "$DOTFILES_DIR" -type f | sed "s|$DOTFILES_DIR/| |" | sort
|
||||
echo " Total: $dotfile_count dotfiles"
|
||||
else
|
||||
echo " No dotfiles"
|
||||
fi
|
||||
else
|
||||
echo " No dotfiles directory"
|
||||
fi
|
||||
|
||||
echo
|
||||
log_info "Managed packages:"
|
||||
if [[ -f "$PACKAGES_FILE" ]]; then
|
||||
echo
|
||||
log_info "Packages:"
|
||||
local package_count=0
|
||||
while IFS= read -r line; do
|
||||
# Skip empty lines and comments
|
||||
[[ -z "$line" || "$line" == \#* ]] && continue
|
||||
while IFS='=' read -r entry; do
|
||||
local section="${entry%%:*}"
|
||||
local key_value="${entry#*:}"
|
||||
local package="${key_value%%=*}"
|
||||
local enabled="${key_value##*=}"
|
||||
|
||||
# Remove whitespace and count as package
|
||||
local package="${line// /}" # Remove whitespace
|
||||
if [[ -n "$package" ]]; then
|
||||
if [[ "$section" == "packages" && "$enabled" == "true" ]]; then
|
||||
echo " $package"
|
||||
((package_count++))
|
||||
fi
|
||||
done <"$PACKAGES_FILE"
|
||||
done < <(parse_toml)
|
||||
|
||||
if [[ $package_count -eq 0 ]]; then
|
||||
echo " No packages"
|
||||
echo " No packages configured"
|
||||
else
|
||||
echo " Total: $package_count packages"
|
||||
fi
|
||||
else
|
||||
echo " No packages file"
|
||||
fi
|
||||
}
|
||||
|
||||
# Show status
|
||||
status() {
|
||||
list_managed
|
||||
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
|
||||
|
|
@ -442,33 +278,27 @@ show_help() {
|
|||
forge <COMMAND>
|
||||
|
||||
COMMANDS:
|
||||
init Initialize configuration structure
|
||||
switch Apply configuration (install packages, deploy dotfiles, cleanup old packages)
|
||||
build Alias for switch
|
||||
add <file> Add a file to dotfiles management
|
||||
remove <path> Remove a dotfile from management
|
||||
list List all managed files and packages with status
|
||||
status Show current status
|
||||
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:
|
||||
- Automatic package cleanup: When you remove a package from packages.toml and run switch,
|
||||
the package will be automatically uninstalled from your system.
|
||||
- Package state tracking: Previously installed packages are tracked to enable cleanup.
|
||||
- Simple format: Packages are managed in simple format (no version tags required).
|
||||
- Random Linux-inspired names: Each list command generates a unique forge name.
|
||||
- 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
|
||||
$DOTFILES_DIR/ Dotfiles to deploy
|
||||
$PACKAGES_FILE Simple file with packages to install (one package per line)
|
||||
$STATE_FILE State tracking file for managed packages and files
|
||||
FILES:
|
||||
$FORGE_DIR/ Main configuration directory
|
||||
$FORGE_TOML Configuration file for packages and dotfiles
|
||||
|
||||
EXAMPLES:
|
||||
forge init
|
||||
forge add ~/.bashrc
|
||||
# Edit $FORGE_TOML to add packages and dotfiles
|
||||
forge switch
|
||||
forge list
|
||||
forge status
|
||||
|
||||
EOF
|
||||
}
|
||||
|
|
@ -482,26 +312,6 @@ main() {
|
|||
"switch")
|
||||
switch
|
||||
;;
|
||||
"build")
|
||||
build
|
||||
;;
|
||||
"add")
|
||||
if [[ -z "${2:-}" ]]; then
|
||||
log_error "Please specify a file to add"
|
||||
exit 1
|
||||
fi
|
||||
add_dotfile "$2"
|
||||
;;
|
||||
"remove")
|
||||
if [[ -z "${2:-}" ]]; then
|
||||
log_error "Please specify a path to remove"
|
||||
exit 1
|
||||
fi
|
||||
remove_dotfile "$2"
|
||||
;;
|
||||
"list")
|
||||
list_managed
|
||||
;;
|
||||
"status")
|
||||
status
|
||||
;;
|
||||
|
|
@ -522,4 +332,3 @@ main() {
|
|||
|
||||
# Run main function with all arguments
|
||||
main "$@"
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue