This commit is contained in:
kumar vaibhav 2025-11-21 19:45:32 +05:30
parent 0cf22d6e5e
commit 980b1c01a8
2 changed files with 336 additions and 524 deletions

289
README.md
View file

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

571
forge
View file

@ -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,288 +122,153 @@ install_packages() {
log_error "Failed to install $package"
fi
fi
done <"$PACKAGES_FILE"
# 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"
fi
}
# Deploy dotfiles
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"
# Create destination directory if it doesn't exist
dest_dir=$(dirname "$dest_file")
mkdir -p "$dest_dir"
# Backup existing file
backup_file "$dest_file"
# Copy file to destination
cp "$src_file" "$dest_file"
log_success "Deployed $rel_path to $dest_file"
# Add to managed files list
update_managed_files "$dest_file"
done
}
# Update managed files list in state
update_managed_files() {
local file="$1"
# 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"
fi
}
# Update installed packages list in state
update_installed_packages() {
local packages=("$@")
done < <(parse_toml)
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"
if [[ "$packages_installed" == false ]]; then
log_info "No packages to install"
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
# 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 - 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
# 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"
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
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
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
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 packages file"
echo " No configuration file found"
fi
}
# Show status
status() {
list_managed
}
# Show help
show_help() {
cat <<EOF
@ -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
;;
@ -521,5 +331,4 @@ main() {
}
# Run main function with all arguments
main "$@"
main "$@"