Compare commits
10 commits
75e8f2db85
...
fd7ff14ac6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd7ff14ac6 | ||
|
|
3969251220 | ||
|
|
25c80fdf3b | ||
|
|
7d51de1052 | ||
|
|
df1f387795 | ||
|
|
da6d24c74f | ||
|
|
359c250101 | ||
|
|
8128e92311 | ||
|
|
47c58ae7eb | ||
|
|
02ac070c2e |
15 changed files with 1957 additions and 638 deletions
75
.gitignore
vendored
Normal file
75
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# Virtual environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Jupyter
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre
|
||||
.pyre/
|
||||
|
||||
# UV
|
||||
uv.lock
|
||||
|
||||
# Backup/config artifacts
|
||||
*.bak
|
||||
inventory.toml.last
|
||||
inventory.toml.rollback
|
||||
353
README.md
353
README.md
|
|
@ -1,78 +1,81 @@
|
|||
# Forge - A Home-Manager Like Tool for Fedora
|
||||
# curator - A Home-Manager Like Tool for Universal Blue builds
|
||||
|
||||
A lightweight configuration management tool for Fedora that provides COPR repository management, dotfile management and package installation functionality through a centralized TOML configuration.
|
||||
A lightweight CLI that provides repository and dotfile management and package installation functionality through a centralized TOML configuration. Ported and expanded from [forge](https://github.com/ijadux2/forge) and inspired by [home-manager](https://github.com/nix-community/home-manager/).
|
||||
|
||||
## Overview
|
||||
|
||||
Forge is a bash script that helps you manage your Fedora system configuration by:
|
||||
curator is a Python CLI that helps you manage your Fedora system configuration by:
|
||||
- Managing COPR repositories (enable/disable automatically)
|
||||
- Installing and managing system packages via dnf
|
||||
- Installing and managing system packages via `dnf` or `rpm-ostree`
|
||||
- Installing and managing userspace packages via `brew`, `flatpak`, or `nix`
|
||||
- Managing dotfiles through symlinks to actual files
|
||||
- Centralized configuration via a single `forge.toml` file
|
||||
- User-level configuration similar to home-manager/nixos
|
||||
- Centralized configuration via a single `inventory.toml` file
|
||||
- Declarative user-level configuration similar to `home-manager`/`nixos`
|
||||
- Automatic backup of existing files before replacement
|
||||
|
||||
## Installation
|
||||
|
||||
1. Clone or download this repository
|
||||
2. Make the script executable:
|
||||
Install the CLI directly from GitHub with [uv](https://github.com/astral-sh/uv):
|
||||
|
||||
```bash
|
||||
chmod +x forge
|
||||
```
|
||||
3. Optionally, move it to a directory in your PATH:
|
||||
```bash
|
||||
sudo mv forge /usr/local/bin/forge
|
||||
uv tool install --from git+https://codeberg.org/randogoth/curator/ curator
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Initialize the configuration structure
|
||||
./forge init
|
||||
curator init
|
||||
|
||||
# Edit the configuration file to add packages and dotfiles
|
||||
nano ~/.config/forge/forge.toml
|
||||
nano ~/.config/curator/inventory.toml
|
||||
|
||||
# Apply configuration
|
||||
./forge switch
|
||||
curator switch
|
||||
|
||||
# Check status
|
||||
./forge status
|
||||
curator status
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
### `init`
|
||||
Initialize the configuration structure and create `forge.toml`.
|
||||
Initialize the configuration structure and create `inventory.toml`.
|
||||
|
||||
```bash
|
||||
./forge init
|
||||
curator init
|
||||
# or ./curator init
|
||||
```
|
||||
|
||||
Creates:
|
||||
- `~/.config/forge/` - Main configuration directory
|
||||
- `~/.config/forge/forge.toml` - Central configuration file
|
||||
- `~/.config/curator/` - Main configuration directory
|
||||
- `~/.config/curator/inventory.toml` - Central configuration file
|
||||
|
||||
### `switch`
|
||||
Apply the current configuration (enable COPR, install packages, deploy dotfiles).
|
||||
Apply the current configuration (enable COPR, install packages, deploy dotfiles). Use `--rollback` to restore the previous `inventory.toml` snapshot before applying.
|
||||
|
||||
```bash
|
||||
./forge switch
|
||||
curator switch
|
||||
# or ./curator switch
|
||||
# rollback to the previous inventory.toml and apply it
|
||||
curator switch --rollback
|
||||
```
|
||||
|
||||
This command:
|
||||
1. Enables all COPR repositories listed in `forge.toml`
|
||||
1. Enables all COPR repositories listed in `inventory.toml`
|
||||
2. Disables COPR repositories that are no longer configured
|
||||
3. Installs all packages listed in `forge.toml`
|
||||
3. Installs all packages listed in `inventory.toml`
|
||||
4. Creates symlinks for all configured dotfiles
|
||||
5. Creates backups of existing files before replacing them
|
||||
6. Updates the last switch timestamp
|
||||
5. Applies environment variables to `~/.config/environment.d/20-curator.conf` and imports them into the user session
|
||||
6. Creates backups of existing files before replacing them
|
||||
7. Updates the last switch timestamp
|
||||
|
||||
### `status`
|
||||
Show current configuration status and information.
|
||||
|
||||
```bash
|
||||
./forge status
|
||||
curator status
|
||||
# or ./curator status
|
||||
```
|
||||
|
||||
Displays:
|
||||
|
|
@ -84,44 +87,99 @@ Displays:
|
|||
Show help message with all available commands.
|
||||
|
||||
```bash
|
||||
./forge help
|
||||
curator help
|
||||
# or ./curator help
|
||||
```
|
||||
|
||||
### `from`
|
||||
Import currently installed packages/repos for a manager into `inventory.toml` so curator can manage them.
|
||||
|
||||
```bash
|
||||
curator from dnf
|
||||
curator from brew
|
||||
curator from flatpak
|
||||
curator from rpm-ostree
|
||||
curator from nix
|
||||
curator from copr
|
||||
```
|
||||
|
||||
### `add` / `remove`
|
||||
Add or remove entries directly in `inventory.toml` using `section:value` pairs. Supports `dnf`, `brew`, `flatpak`, `rpm-ostree`, `nix`, and `copr`.
|
||||
|
||||
```bash
|
||||
curator add dnf:uv nix:micro
|
||||
curator remove dnf:curl flatpak:org.mozilla.firefox
|
||||
```
|
||||
|
||||
### `reset`
|
||||
Remove the rollback snapshot (`inventory.toml.rollback`).
|
||||
|
||||
```bash
|
||||
curator reset
|
||||
```
|
||||
|
||||
### `env`
|
||||
Apply environment variables defined in `[variables]` and propagate them to systemd/DBus. For your current shell, run:
|
||||
|
||||
```bash
|
||||
curator env
|
||||
# then, to update this shell:
|
||||
eval "$(curator env --eval-only)"
|
||||
```
|
||||
|
||||
Use `curator env --eval-only` in shell init files if you want each shell to export the current `[variables]` values without touching `environment.d` or systemd/DBus.
|
||||
|
||||
## Configuration
|
||||
|
||||
### forge.toml
|
||||
The central configuration file located at `~/.config/forge/forge.toml`:
|
||||
### inventory.toml
|
||||
The central configuration file located at `~/.config/curator/inventory.toml`:
|
||||
|
||||
```toml
|
||||
# Forge Configuration File
|
||||
# curator Configuration File
|
||||
# User-level configuration similar to home-manager/nixos
|
||||
|
||||
[forge]
|
||||
[curator]
|
||||
version = "1.0"
|
||||
last_switch = null
|
||||
last_switch = ""
|
||||
|
||||
[copr]
|
||||
# COPR repositories to enable
|
||||
# Just list COPR repository names - presence means enable, absence means don't enable
|
||||
copr.fedorainfracloud.org/username/repository
|
||||
copr.fedorainfracloud.org/anotheruser/anotherrepo
|
||||
# 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
|
||||
git
|
||||
vim
|
||||
curl
|
||||
wget
|
||||
[dnf]
|
||||
# git
|
||||
# vim
|
||||
# curl
|
||||
# wget
|
||||
|
||||
[brew]
|
||||
# wget
|
||||
# coreutils
|
||||
|
||||
[flatpak]
|
||||
# org.mozilla.firefox
|
||||
# com.spotify.Client
|
||||
|
||||
[rpm-ostree]
|
||||
# podman
|
||||
# htop
|
||||
|
||||
[nix]
|
||||
# nixpkgs#git # or just "git" (curator will prefix nixpkgs#)
|
||||
# nixpkgs#htop # or just "htop"
|
||||
|
||||
[variables]
|
||||
# ENV_VAR = "value"
|
||||
# ANOTHER = "another value"
|
||||
|
||||
[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"
|
||||
# source_path: where the actual file is stored (relative to dotfiles directory)
|
||||
".bashrc" = ".bashrc"
|
||||
".config/vimrc" = "vimrc"
|
||||
".config/alacritty/alacritty.yml" = "alacritty.yml"
|
||||
|
||||
[options]
|
||||
# Additional options
|
||||
|
|
@ -131,12 +189,12 @@ backup_dir = "backup"
|
|||
|
||||
### Configuration Sections
|
||||
|
||||
#### `[forge]`
|
||||
#### `[curator]`
|
||||
- `version`: Configuration file version
|
||||
- `last_switch`: Timestamp of last switch operation (auto-updated)
|
||||
|
||||
#### `[copr]`
|
||||
List of COPR repositories to enable via `dnf copr enable`. **Just list COPR repository names** - presence means enable, absence means don't enable.
|
||||
List of COPR repositories to enable via `dnf copr enable`. **One repository per line**—presence means enable, absence means don't enable.
|
||||
|
||||
**Examples:**
|
||||
```toml
|
||||
|
|
@ -145,14 +203,14 @@ copr.fedorainfracloud.org/username/repository
|
|||
copr.fedorainfracloud.org/anotheruser/anotherrepo
|
||||
```
|
||||
|
||||
To remove a COPR repository, simply delete the line containing the repository name. Forge will automatically disable it during the next switch.
|
||||
To remove a COPR repository, simply delete the line containing the repository name. curator will automatically disable it during the next switch.
|
||||
|
||||
#### `[packages]`
|
||||
List of packages to install via dnf. **Just list package names** - presence means install, absence means don't install.
|
||||
#### `[dnf]`
|
||||
List of packages to install via dnf. **One package per line**—presence means install, absence means don't install.
|
||||
|
||||
**Examples:**
|
||||
```toml
|
||||
[packages]
|
||||
[dnf]
|
||||
git
|
||||
vim
|
||||
curl
|
||||
|
|
@ -163,21 +221,49 @@ npm
|
|||
|
||||
To remove a package, simply delete the line containing the package name.
|
||||
|
||||
#### `[brew]`
|
||||
List of packages to install via Homebrew. **One package per line**—presence means install.
|
||||
|
||||
#### `[flatpak]`
|
||||
List of Flatpak refs to install. **One ref per line**—presence means install.
|
||||
|
||||
#### `[rpm-ostree]`
|
||||
List of rpm-ostree layered packages. **One package per line**—presence means install.
|
||||
|
||||
#### `[nix]`
|
||||
List of nix packages. **One package per line**—presence means install. You can specify plain names (e.g., `neovim`) or `nixpkgs#name`; curator will prefix `nixpkgs#` for installs and use the base name for removals.
|
||||
|
||||
#### `[variables]`
|
||||
User environment variables to set via systemd `environment.d`. curator writes them to `~/.config/environment.d/20-curator.conf` during `curator switch`; new sessions will load them automatically.
|
||||
|
||||
**Examples:**
|
||||
```toml
|
||||
[variables]
|
||||
EDITOR = "nvim"
|
||||
PAGER = "less -R"
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Values are imported into the user systemd and DBus environments during `curator switch` or `curator env`.
|
||||
- To update a currently running shell, run `eval "$(curator env --eval-only)"` after applying.
|
||||
- Later shell init files (e.g., `.bashrc`, `.zshrc`) can still override these values.
|
||||
|
||||
#### `[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)
|
||||
- **Value**: Source path where actual file is stored (relative to dotfiles directory)
|
||||
- Note: Source path automatically prefixed with "dotfiles/" if not present
|
||||
|
||||
#### `[options]`
|
||||
Additional configuration options:
|
||||
- `backup`: Enable/disable backup of existing files (default: true)
|
||||
- `backup_dir`: Directory name for backups (relative to forge directory)
|
||||
- `backup_dir`: Directory name for backups (relative to curator directory)
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
~/.config/forge/
|
||||
├── forge.toml # Main configuration file
|
||||
~/.config/curator/
|
||||
├── inventory.toml # Main configuration file
|
||||
├── dotfiles/ # Your actual dotfiles (source files)
|
||||
│ ├── .bashrc
|
||||
│ ├── vimrc
|
||||
|
|
@ -187,153 +273,6 @@ Additional configuration options:
|
|||
└── vimrc.20231121_143022.bak
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
### COPR Repository Management
|
||||
COPR repositories are managed through the `[copr]` section in `forge.toml`:
|
||||
- **Add COPR repo**: Simply add the repository name on a new line
|
||||
- **Remove COPR repo**: Delete the line containing the repository name
|
||||
- **Automatic cleanup**: Forge automatically disables COPR repos that are removed from configuration
|
||||
- **No flags needed**: Just presence/absence of the repository name matters
|
||||
|
||||
### Package Management
|
||||
Packages are managed through the `[packages]` section in `forge.toml`:
|
||||
- **Add package**: Simply add the package name on a new line
|
||||
- **Remove package**: Delete the line containing the package name
|
||||
- **No flags needed**: Just presence/absence of the package name matters
|
||||
|
||||
### 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
|
||||
|
||||
### Backup System
|
||||
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 forge
|
||||
./forge init
|
||||
|
||||
# Edit forge.toml to add COPR repos and packages
|
||||
nano ~/.config/forge/forge.toml
|
||||
|
||||
# Add to [copr] section:
|
||||
copr.fedorainfracloud.org/username/cool-repo
|
||||
|
||||
# Add to [packages] section:
|
||||
git
|
||||
vim
|
||||
curl
|
||||
|
||||
# Create your dotfiles directory and add files
|
||||
mkdir -p ~/.config/forge/dotfiles
|
||||
echo "export EDITOR=vim" > ~/.config/forge/dotfiles/.bashrc
|
||||
|
||||
# Add to [dotfiles] section:
|
||||
".bashrc" = "dotfiles/.bashrc"
|
||||
|
||||
# Apply configuration
|
||||
./forge switch
|
||||
```
|
||||
|
||||
### Managing Application Configurations
|
||||
```bash
|
||||
# Add alacritty configuration
|
||||
mkdir -p ~/.config/forge/dotfiles/.config/alacritty
|
||||
cp ~/.config/alacritty/alacritty.yml ~/.config/forge/dotfiles/.config/alacritty/
|
||||
|
||||
# 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
|
||||
```
|
||||
|
||||
### COPR Repository Management Examples
|
||||
```toml
|
||||
[copr]
|
||||
# Development tools COPR
|
||||
copr.fedorainfracloud.org/development/tools
|
||||
copr.fedorainfracloud.org/user/neovim-nightly
|
||||
|
||||
# To remove a COPR repo, just delete the line
|
||||
# Forge will automatically disable it during the next switch
|
||||
```
|
||||
|
||||
### Package Management Examples
|
||||
```toml
|
||||
[packages]
|
||||
# Development tools
|
||||
git
|
||||
vim
|
||||
nodejs
|
||||
npm
|
||||
|
||||
# System utilities
|
||||
curl
|
||||
wget
|
||||
tree
|
||||
htop
|
||||
|
||||
# To remove a package, just delete the line
|
||||
# For example, to remove htop, delete the "htop" line
|
||||
```
|
||||
|
||||
### Version Control Your Configuration
|
||||
```bash
|
||||
# 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"
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
- `FORGE_DIR`: Override the default configuration directory (default: `~/.config/forge`)
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `dnf` - Fedora package manager
|
||||
- `dnf-plugins-core` - For COPR repository management
|
||||
- `sed` - For updating configuration file (usually pre-installed)
|
||||
|
||||
## Migration from Previous Version
|
||||
|
||||
If you were using the old version of forge with `git = true` format:
|
||||
|
||||
1. Your existing configuration will not be automatically migrated
|
||||
2. Run `./forge init` to create the new `forge.toml` structure
|
||||
3. Convert your package configuration from:
|
||||
```toml
|
||||
# Old format
|
||||
git = true
|
||||
vim = true
|
||||
```
|
||||
to:
|
||||
```toml
|
||||
# New format
|
||||
git
|
||||
vim
|
||||
```
|
||||
4. Move your existing dotfiles from the old directory to `~/.config/forge/dotfiles/`
|
||||
|
||||
## License
|
||||
|
||||
This project is open source. Feel free to contribute or report issues.
|
||||
- `CURATOR_DIR`: Override the default configuration directory (default: `~/.config/curator`)
|
||||
|
|
|
|||
24
curator
Executable file
24
curator
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main() -> None:
|
||||
here = Path(__file__).resolve().parent
|
||||
src_dir = here / "src"
|
||||
if src_dir.exists():
|
||||
sys.path.insert(0, str(src_dir))
|
||||
|
||||
try:
|
||||
from curator.cli import main as curator_main
|
||||
except ImportError as exc: # pragma: no cover - fallback error path
|
||||
sys.stderr.write(f"Failed to import curator CLI: {exc}\n")
|
||||
sys.exit(1)
|
||||
|
||||
curator_main()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
429
forge
429
forge
|
|
@ -1,429 +0,0 @@
|
|||
#!/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
|
||||
|
||||
[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)
|
||||
# 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 for non-packages sections
|
||||
if [[ "$section" != "packages" && "$line" =~ ^([^=]+)=(.+)$ ]]; then
|
||||
local key="${BASH_REMATCH[1]// /}"
|
||||
local value="${BASH_REMATCH[2]// /}"
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
# 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 "$@"
|
||||
20
pyproject.toml
Normal file
20
pyproject.toml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
[project]
|
||||
name = "curator"
|
||||
version = "0.1.1"
|
||||
description = "A home-manager style Fedora configuration helper."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = []
|
||||
|
||||
[project.scripts]
|
||||
curator = "curator.cli:main"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.uv]
|
||||
package = true
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["pytest>=7.4"]
|
||||
1
src/curator/__init__.py
Normal file
1
src/curator/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
# curator package
|
||||
BIN
src/curator/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
src/curator/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
src/curator/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
src/curator/__pycache__/__init__.cpython-314.pyc
Normal file
Binary file not shown.
BIN
src/curator/__pycache__/cli.cpython-313.pyc
Normal file
BIN
src/curator/__pycache__/cli.cpython-313.pyc
Normal file
Binary file not shown.
BIN
src/curator/__pycache__/cli.cpython-314.pyc
Normal file
BIN
src/curator/__pycache__/cli.cpython-314.pyc
Normal file
Binary file not shown.
1334
src/curator/cli.py
Normal file
1334
src/curator/cli.py
Normal file
File diff suppressed because it is too large
Load diff
BIN
tests/__pycache__/test_config.cpython-313-pytest-9.0.2.pyc
Normal file
BIN
tests/__pycache__/test_config.cpython-313-pytest-9.0.2.pyc
Normal file
Binary file not shown.
262
tests/test_config.py
Normal file
262
tests/test_config.py
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
from pathlib import Path
|
||||
|
||||
import datetime as dt
|
||||
|
||||
from curator import cli
|
||||
|
||||
|
||||
def test_parse_config_line_format(tmp_path: Path) -> None:
|
||||
config_path = tmp_path / "inventory.toml"
|
||||
config_path.write_text(
|
||||
"""
|
||||
[curator]
|
||||
version = "1.0"
|
||||
last_switch = ""
|
||||
|
||||
[copr]
|
||||
copr.fedorainfracloud.org/user/repo
|
||||
|
||||
[dnf]
|
||||
git
|
||||
vim
|
||||
|
||||
[brew]
|
||||
wget
|
||||
|
||||
[flatpak]
|
||||
org.mozilla.firefox
|
||||
|
||||
[rpm-ostree]
|
||||
podman
|
||||
|
||||
[nix]
|
||||
nixpkgs#git
|
||||
|
||||
[dotfiles]
|
||||
".bashrc" = ".bashrc"
|
||||
|
||||
[options]
|
||||
backup = true
|
||||
backup_dir = "backup"
|
||||
""".strip()
|
||||
)
|
||||
|
||||
config = cli.parse_config(config_path)
|
||||
|
||||
assert config.copr == ["copr.fedorainfracloud.org/user/repo"]
|
||||
assert config.packages == ["git", "vim"]
|
||||
assert config.brew_packages == ["wget"]
|
||||
assert config.flatpak_refs == ["org.mozilla.firefox"]
|
||||
assert config.rpm_ostree_packages == ["podman"]
|
||||
assert config.nix_packages == ["nixpkgs#git"]
|
||||
assert config.dotfiles == {".bashrc": ".bashrc"}
|
||||
assert config.options["backup"] is True
|
||||
|
||||
|
||||
def test_parse_config_legacy_format(tmp_path: Path) -> None:
|
||||
config_path = tmp_path / "inventory.toml"
|
||||
config_path.write_text(
|
||||
"""
|
||||
[dnf]
|
||||
git
|
||||
vim
|
||||
|
||||
[brew]
|
||||
wget
|
||||
|
||||
[flatpak]
|
||||
org.mozilla.firefox
|
||||
|
||||
[rpm-ostree]
|
||||
podman
|
||||
|
||||
[nix]
|
||||
nixpkgs#git
|
||||
|
||||
[variables]
|
||||
EDITOR = "nvim"
|
||||
|
||||
[copr]
|
||||
copr.fedorainfracloud.org/user/repo
|
||||
|
||||
[dotfiles]
|
||||
".bashrc" = ".bashrc"
|
||||
""".strip()
|
||||
)
|
||||
|
||||
config = cli.parse_config(config_path)
|
||||
|
||||
assert config.packages == ["git", "vim"]
|
||||
assert config.brew_packages == ["wget"]
|
||||
assert config.flatpak_refs == ["org.mozilla.firefox"]
|
||||
assert config.rpm_ostree_packages == ["podman"]
|
||||
assert config.nix_packages == ["nixpkgs#git"]
|
||||
assert config.copr == ["copr.fedorainfracloud.org/user/repo"]
|
||||
assert config.dotfiles == {".bashrc": ".bashrc"}
|
||||
assert config.variables == {"EDITOR": "nvim"}
|
||||
|
||||
|
||||
def test_diff_items() -> None:
|
||||
added, removed = cli.diff_items(["a", "b"], ["b", "c"])
|
||||
assert added == ["a"]
|
||||
assert removed == ["c"]
|
||||
|
||||
|
||||
def test_update_last_switch_creates_backup(tmp_path: Path) -> None:
|
||||
config_dir = tmp_path
|
||||
config_path = config_dir / "inventory.toml"
|
||||
config_path.write_text(
|
||||
"""
|
||||
[curator]
|
||||
version = "1.0"
|
||||
last_switch = ""
|
||||
""".strip()
|
||||
)
|
||||
|
||||
options = {"backup": True, "backup_dir": "backup"}
|
||||
cli.update_last_switch(config_path, options)
|
||||
|
||||
lines = [line.strip() for line in config_path.read_text().splitlines() if line.strip()]
|
||||
last_switch_line = [line for line in lines if line.startswith("last_switch")]
|
||||
assert last_switch_line, "last_switch should be written"
|
||||
ts_str = last_switch_line[0].split("=", 1)[1].strip().strip('"')
|
||||
dt.datetime.fromisoformat(ts_str)
|
||||
|
||||
backup_dir = config_dir / "backup"
|
||||
backups = list(backup_dir.iterdir())
|
||||
assert backups, "expected a backup of inventory.toml to be created"
|
||||
assert any(path.name.startswith("inventory.toml.") for path in backups)
|
||||
|
||||
def test_reset_previous_config(tmp_path: Path, monkeypatch) -> None:
|
||||
rollback = tmp_path / "inventory.toml.rollback"
|
||||
rollback.write_text("prev")
|
||||
|
||||
def fake_get_paths():
|
||||
return tmp_path, tmp_path / "inventory.toml", tmp_path / "inventory.toml.last", rollback
|
||||
|
||||
monkeypatch.setattr(cli, "get_paths", fake_get_paths)
|
||||
cli.reset_previous_config()
|
||||
assert not rollback.exists()
|
||||
|
||||
|
||||
def test_update_section_entries_rewrites_section(tmp_path: Path) -> None:
|
||||
config_path = tmp_path / "inventory.toml"
|
||||
config_path.write_text(
|
||||
"""
|
||||
[dnf]
|
||||
git
|
||||
|
||||
[copr]
|
||||
copr.fedorainfracloud.org/user/repo
|
||||
|
||||
[brew]
|
||||
wget
|
||||
""".strip()
|
||||
)
|
||||
|
||||
cli.update_section_entries(config_path, "dnf", ["vim", "curl", "vim"])
|
||||
cli.update_section_entries(config_path, "copr", ["copr.fedorainfracloud.org/user/repo", "copr.fedorainfracloud.org/another/repo"])
|
||||
|
||||
content = config_path.read_text().strip().splitlines()
|
||||
dnf_index = content.index("[dnf]")
|
||||
assert content[dnf_index + 1 : dnf_index + 3] == ["curl", "vim"]
|
||||
assert "[copr]" in content
|
||||
copr_index = content.index("[copr]")
|
||||
assert content[copr_index + 1 : copr_index + 3] == [
|
||||
"copr.fedorainfracloud.org/another/repo",
|
||||
"copr.fedorainfracloud.org/user/repo",
|
||||
]
|
||||
|
||||
|
||||
def test_merge_section_entries_preserves_spacing(tmp_path: Path) -> None:
|
||||
config_path = tmp_path / "inventory.toml"
|
||||
config_path.write_text(
|
||||
"""
|
||||
[dnf]
|
||||
git
|
||||
|
||||
[brew]
|
||||
wget
|
||||
""".strip()
|
||||
)
|
||||
|
||||
cli.merge_section_entries(config_path, "dnf", {"vim"}, set())
|
||||
|
||||
lines = config_path.read_text().splitlines()
|
||||
assert "" in lines, "expected to keep a blank line between sections"
|
||||
dnf_index = lines.index("[dnf]")
|
||||
brew_index = lines.index("[brew]")
|
||||
assert lines[dnf_index + 1 : dnf_index + 3] == ["git", "vim"]
|
||||
assert lines[brew_index - 1] == ""
|
||||
|
||||
|
||||
def test_apply_environment_variables(tmp_path: Path, monkeypatch) -> None:
|
||||
curator_dir = tmp_path / "curator"
|
||||
curator_dir.mkdir()
|
||||
env_home = tmp_path / "xdg"
|
||||
monkeypatch.setenv("XDG_CONFIG_HOME", str(env_home))
|
||||
|
||||
config = cli.empty_config()
|
||||
config.variables = {"EDITOR": "nvim", "WITH_SPACE": "some value"}
|
||||
config.options["backup"] = False
|
||||
|
||||
written, removed = cli.apply_environment_variables(config, curator_dir)
|
||||
|
||||
env_file = env_home / "environment.d" / "20-curator.conf"
|
||||
assert env_file.exists()
|
||||
lines = env_file.read_text().splitlines()
|
||||
assert "EDITOR=nvim" in lines
|
||||
assert 'WITH_SPACE="some value"' in lines
|
||||
assert written == {"EDITOR": "nvim", "WITH_SPACE": "some value"}
|
||||
assert removed == set()
|
||||
|
||||
|
||||
def test_apply_environment_variables_removes_file_when_empty(tmp_path: Path, monkeypatch) -> None:
|
||||
curator_dir = tmp_path / "curator"
|
||||
curator_dir.mkdir()
|
||||
env_home = tmp_path / "xdg"
|
||||
env_dir = env_home / "environment.d"
|
||||
env_dir.mkdir(parents=True, exist_ok=True)
|
||||
env_file = env_dir / "20-curator.conf"
|
||||
env_file.write_text("OLD=1\n")
|
||||
monkeypatch.setenv("XDG_CONFIG_HOME", str(env_home))
|
||||
|
||||
config = cli.empty_config()
|
||||
config.options["backup"] = False
|
||||
|
||||
variables, removed = cli.apply_environment_variables(config, curator_dir)
|
||||
|
||||
assert not env_file.exists()
|
||||
assert variables == {}
|
||||
assert removed == {"OLD"}
|
||||
|
||||
|
||||
def test_env_command_eval_outputs_exports(tmp_path: Path, monkeypatch, capsys) -> None:
|
||||
curator_dir = tmp_path / "curator"
|
||||
curator_dir.mkdir()
|
||||
env_file = curator_dir / "inventory.toml"
|
||||
env_file.write_text(
|
||||
"""
|
||||
[variables]
|
||||
EDITOR = "nvim"
|
||||
""".strip()
|
||||
)
|
||||
env_home = tmp_path / "xdg"
|
||||
env_dir = env_home / "environment.d"
|
||||
env_dir.mkdir(parents=True, exist_ok=True)
|
||||
env_conf = env_dir / "20-curator.conf"
|
||||
env_conf.write_text("OLD=1\n")
|
||||
monkeypatch.setenv("XDG_CONFIG_HOME", str(env_home))
|
||||
|
||||
def fake_get_paths():
|
||||
return curator_dir, env_file, curator_dir / "last", curator_dir / "rollback"
|
||||
|
||||
def fail_apply_environment_variables(*_args, **_kwargs):
|
||||
raise AssertionError("apply_environment_variables should not be called in eval-only mode")
|
||||
|
||||
monkeypatch.setattr(cli, "get_paths", fake_get_paths)
|
||||
monkeypatch.setattr(cli, "apply_environment_variables", fail_apply_environment_variables)
|
||||
|
||||
cli.env_command(eval_only=True)
|
||||
out = capsys.readouterr().out.strip().splitlines()
|
||||
assert out == ["export EDITOR=nvim", "unset OLD"]
|
||||
14
tmpcfg.toml
Normal file
14
tmpcfg.toml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
[curator]
|
||||
version = "1.0"
|
||||
last_switch = ""
|
||||
|
||||
copr = ["copr.fedorainfracloud.org/user/repo"]
|
||||
packages = ["git", "vim"]
|
||||
|
||||
[dotfiles]
|
||||
".bashrc" = ".bashrc"
|
||||
|
||||
[options]
|
||||
backup = true
|
||||
backup_dir = "backup"
|
||||
79
uv.lock
generated
Normal file
79
uv.lock
generated
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.11"
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curator"
|
||||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "pytest" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [{ name = "pytest", specifier = ">=7.4" }]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "25.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.19.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "9.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "iniconfig" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pluggy" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue