init
This commit is contained in:
parent
359c250101
commit
da6d24c74f
13 changed files with 973 additions and 555 deletions
482
forge
482
forge
|
|
@ -1,472 +1,24 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
# forge - A home-manager like script for Fedora
|
||||
# Provides dotfile management and package installation functionality
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Configuration
|
||||
FORGE_DIR="${FORGE_DIR:-$HOME/.config/forge}"
|
||||
FORGE_TOML="$FORGE_DIR/forge.toml"
|
||||
def main() -> None:
|
||||
here = Path(__file__).resolve().parent
|
||||
src_dir = here / "src"
|
||||
if src_dir.exists():
|
||||
sys.path.insert(0, str(src_dir))
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
try:
|
||||
from forge.cli import main as forge_main
|
||||
except ImportError as exc: # pragma: no cover - fallback error path
|
||||
sys.stderr.write(f"Failed to import forge CLI: {exc}\n")
|
||||
sys.exit(1)
|
||||
|
||||
# Logging functions
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
forge_main()
|
||||
|
||||
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
|
||||
|
||||
[copr]
|
||||
# COPR repositories to enable
|
||||
# Just list COPR repository names - presence means enable, absence means don't enable
|
||||
# Examples:
|
||||
# copr.fedorainfracloud.org/username/repository
|
||||
# copr.fedorainfracloud.org/anotheruser/anotherrepo
|
||||
|
||||
[packages]
|
||||
# List of packages to install using dnf
|
||||
# Just list package names - presence means install, absence means don't install
|
||||
# Examples:
|
||||
# git
|
||||
# vim
|
||||
# curl
|
||||
|
||||
[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)
|
||||
# Note: source_path automatically prefixed with "dotfiles/" if not present
|
||||
# Examples:
|
||||
# ".bashrc" = ".bashrc"
|
||||
# ".config/vimrc" = "vimrc"
|
||||
# ".config/alacritty/alacritty.yml" = "alacritty.yml"
|
||||
|
||||
[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 for non-packages sections
|
||||
if [[ "$section" != "packages" && "$line" =~ ^([^=]+)=(.+)$ ]]; then
|
||||
local key="${BASH_REMATCH[1]}"
|
||||
local value="${BASH_REMATCH[2]}"
|
||||
|
||||
# Trim leading/trailing whitespace from key and value
|
||||
key=$(echo "$key" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||
value=$(echo "$value" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
|
||||
|
||||
# Remove quotes from quoted strings
|
||||
if [[ "$key" =~ ^\"(.*)\"$ ]]; then
|
||||
key="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
if [[ "$value" =~ ^\"(.*)\"$ ]]; then
|
||||
value="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
|
||||
echo "$section:$key=$value"
|
||||
# Parse package names (just keys without values in packages section)
|
||||
elif [[ "$section" == "packages" && "$line" =~ ^([^=]+)$ ]]; then
|
||||
local package="${BASH_REMATCH[1]// /}"
|
||||
echo "$section:$package"
|
||||
# Parse COPR repository names (just keys without values in copr section)
|
||||
elif [[ "$section" == "copr" && "$line" =~ ^([^=]+)$ ]]; then
|
||||
local copr_repo="${BASH_REMATCH[1]// /}"
|
||||
echo "$section:$copr_repo"
|
||||
fi
|
||||
done <"$FORGE_TOML"
|
||||
}
|
||||
|
||||
# Enable COPR repositories from forge.toml
|
||||
enable_copr_repos() {
|
||||
log_info "Enabling COPR repositories..."
|
||||
|
||||
local copr_enabled=false
|
||||
|
||||
while IFS='=' read -r entry; do
|
||||
local section="${entry%%:*}"
|
||||
local copr_repo="${entry#*:}"
|
||||
|
||||
if [[ "$section" == "copr" && -n "$copr_repo" ]]; then
|
||||
copr_enabled=true
|
||||
log_info "Enabling COPR repository: $copr_repo"
|
||||
if sudo dnf copr enable -y "$copr_repo"; then
|
||||
log_success "Enabled COPR: $copr_repo"
|
||||
else
|
||||
log_error "Failed to enable COPR: $copr_repo"
|
||||
fi
|
||||
fi
|
||||
done < <(parse_toml)
|
||||
|
||||
if [[ "$copr_enabled" == false ]]; then
|
||||
log_info "No COPR repositories to enable"
|
||||
fi
|
||||
}
|
||||
|
||||
# Disable COPR repositories that are no longer in forge.toml
|
||||
disable_copr_repos() {
|
||||
log_info "Checking for COPR repositories to disable..."
|
||||
|
||||
# Get currently enabled COPR repos
|
||||
local enabled_repos=$(sudo dnf copr list --enabled 2>/dev/null | grep -E "copr\.fedorainfracloud\.org" | awk '{print $1}' || true)
|
||||
|
||||
# Get configured COPR repos from forge.toml
|
||||
local configured_repos=""
|
||||
while IFS='=' read -r entry; do
|
||||
local section="${entry%%:*}"
|
||||
local copr_repo="${entry#*:}"
|
||||
|
||||
if [[ "$section" == "copr" && -n "$copr_repo" ]]; then
|
||||
configured_repos="$configured_repos $copr_repo"
|
||||
fi
|
||||
done < <(parse_toml)
|
||||
|
||||
# Disable repos that are enabled but not configured
|
||||
for repo in $enabled_repos; do
|
||||
if [[ ! " $configured_repos " =~ " $repo " ]]; then
|
||||
log_info "Disabling COPR repository: $repo"
|
||||
if sudo dnf copr disable -y "$repo"; then
|
||||
log_success "Disabled COPR: $repo"
|
||||
else
|
||||
log_error "Failed to disable COPR: $repo"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# 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 package="${entry#*:}"
|
||||
|
||||
if [[ "$section" == "packages" && -n "$package" ]]; 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
|
||||
local dotfiles_dir="$FORGE_DIR/dotfiles"
|
||||
|
||||
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
|
||||
|
||||
# Normalize source path - if it doesn't start with dotfiles/, add it
|
||||
if [[ ! "$source" =~ ^dotfiles/ ]]; then
|
||||
source="dotfiles/$source"
|
||||
fi
|
||||
|
||||
local target_path="$HOME/$target"
|
||||
local source_path="$FORGE_DIR/$source"
|
||||
|
||||
# Verify source file exists
|
||||
if [[ ! -f "$source_path" && ! -d "$source_path" ]]; then
|
||||
log_error "Source file not found: $source_path"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Create target directory if it doesn't exist
|
||||
local target_dir=$(dirname "$target_path")
|
||||
mkdir -p "$target_dir"
|
||||
|
||||
# Handle existing file/symlink
|
||||
if [[ -e "$target_path" || -L "$target_path" ]]; then
|
||||
# Check if it's already a correct symlink
|
||||
if [[ -L "$target_path" ]]; then
|
||||
local current_target=$(readlink "$target_path")
|
||||
if [[ "$current_target" == "$source_path" ]]; then
|
||||
log_info "Symlink already correct: $target_path"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
# Backup existing file if it exists and is not a symlink
|
||||
if [[ -e "$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 -r "$target_path" "$backup_path"
|
||||
log_info "Backed up $target_path to $backup_path"
|
||||
fi
|
||||
|
||||
# Remove existing file/symlink
|
||||
rm -rf "$target_path"
|
||||
fi
|
||||
|
||||
# Create relative symlink for better portability
|
||||
local relative_source
|
||||
if [[ -d "$source_path" ]]; then
|
||||
relative_source=$(realpath --relative-to="$target_dir" "$source_path")
|
||||
else
|
||||
relative_source=$(realpath --relative-to="$target_dir" "$source_path")
|
||||
fi
|
||||
|
||||
# Create symlink using relative path
|
||||
if ln -s "$relative_source" "$target_path"; then
|
||||
log_success "Created symlink: $target_path -> $relative_source"
|
||||
else
|
||||
log_error "Failed to create symlink: $target_path -> $relative_source"
|
||||
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
|
||||
|
||||
# Enable COPR repositories
|
||||
enable_copr_repos
|
||||
|
||||
# Disable COPR repositories that are no longer configured
|
||||
disable_copr_repos
|
||||
|
||||
# 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 "COPR Repositories:"
|
||||
local copr_count=0
|
||||
while IFS='=' read -r entry; do
|
||||
local section="${entry%%:*}"
|
||||
local copr_repo="${entry#*:}"
|
||||
|
||||
if [[ "$section" == "copr" && -n "$copr_repo" ]]; then
|
||||
echo " $copr_repo"
|
||||
((copr_count++))
|
||||
fi
|
||||
done < <(parse_toml)
|
||||
|
||||
if [[ $copr_count -eq 0 ]]; then
|
||||
echo " No COPR repositories configured"
|
||||
else
|
||||
echo " Total: $copr_count COPR repositories"
|
||||
fi
|
||||
|
||||
echo
|
||||
log_info "Packages:"
|
||||
local package_count=0
|
||||
while IFS='=' read -r entry; do
|
||||
local section="${entry%%:*}"
|
||||
local package="${entry#*:}"
|
||||
|
||||
if [[ "$section" == "packages" && -n "$package" ]]; 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 (enable COPR, install packages, deploy dotfiles)
|
||||
status Show current configuration status
|
||||
help Show this help message
|
||||
|
||||
FEATURES:
|
||||
- TOML-based configuration in forge.toml
|
||||
- COPR repository management (enable/disable automatically)
|
||||
- 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 COPR, packages and dotfiles
|
||||
|
||||
EXAMPLES:
|
||||
forge init
|
||||
# Edit $FORGE_TOML to add COPR repos, 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 "$@"
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue