fest

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 6, 2026 License: GPL-3.0 Imports: 22 Imported by: 0

README

Fest

Go Reference Go Report Card License

A declarative system configuration management framework for Arch Linux written in Go.

Note: This is a complete rewrite from the original Ruby implementation. The Go version provides better performance, type safety, and easier distribution as a single binary.

Overview

fest allows you to declare your entire system state (packages, services, files, configurations) as Go code, and synchronizes your Arch Linux system to match that declared state. Think of it as a type-safe, compiled alternative to configuration management tools like Ansible or NixOS, specifically tailored for Arch Linux.

Features

  • Declarative Configuration: Define what you want, not how to get it
  • Package Management: Manages pacman packages (including AUR via embedded yay)
  • Multiple Package Managers: Support for Flatpak, npm, Go packages, Ruby gems
  • System Configuration: Timezone, locale, keyboard settings
  • Systemd Services: Enable/disable system and user services, timers, and sockets
  • User Groups: Manage user group memberships
  • System Files: Deploy and track system configuration files
  • Dotfile Management: GNU Stow integration for user dotfiles
  • Broken Symlink Cleanup: Automatically detect and remove broken symlinks
  • Two-Phase Execution: Preview changes before applying (diff mode)
  • State Tracking: Knows what it installed and can clean up unwanted resources
  • Dependency Awareness: Won't remove packages that other wanted packages depend on

Installation

Prerequisites

Before using this framework, ensure you have:

  1. Arch Linux system
  2. Go 1.25+ installed
  3. stow for dotfile management

Note: This project includes an embedded copy of yay for AUR package management. No separate yay installation is required.

Quick Start
  1. Create a new Go module for your system configuration:
mkdir -p ~/mysystem
cd ~/mysystem
go mod init mysystem
go get github.com/emad-elsaid/fest
  1. Create your configuration file (e.g., main.go):
package main

import "github.com/emad-elsaid/fest"

func init() {
    // Declare packages
    fest.Package(
        "vim",
        "git",
        "docker",
        "firefox",
    )

    // Enable services
    fest.SystemService("docker")

    // Configure system
    fest.Timedate("America/New_York", true)
    fest.Locale("en_US.UTF-8 UTF-8")
}

func main() {
    fest.Main()
}
  1. Run commands:
# Preview what would change
go run . diff

# Apply configuration
go run . apply

# Save current system state back to Go files
go run . save

Usage

Commands
  • apply: Synchronize your system to match the declared configuration

    • Installs missing packages
    • Enables/disables services
    • Deploys system files
    • Removes unwanted resources (with confirmation)
  • diff: Show what would change without making any modifications

    • Useful for previewing changes before applying
    • Safe to run anytime
  • save: Capture current system state as Go code

    • Generates .go files with function calls that match your system
    • Useful after manual installations to capture them declaratively
Package Management
Pacman Packages
// Individual packages
fest.Package("vim", "git", "docker")

// Package groups
fest.PackageGroup("base-devel")
Flatpak Applications
fest.Flatpak(
    "com.slack.Slack",
    "org.mozilla.firefox",
)
NPM Packages (Global)
fest.NpmPackage(
    "typescript",
    "@vue/cli",
    "eslint@8.50.0",  // Version pinning
)
Go Packages
fest.GoPackage(
    "github.com/golangci/golangci-lint/cmd/golangci-lint@latest",
    "golang.org/x/tools/cmd/goimports",
)
Ruby Gems
fest.RubyGem(
    "bundler",
    "rails@7.0.0",  // Version pinning
)
System Configuration
Timezone and NTP
fest.Timedate("America/New_York", true)  // timezone, enable NTP
Locale
fest.Locale("en_US.UTF-8 UTF-8")
Keyboard
fest.Keyboard(
    "us",        // keymap
    "us",        // layout
    "pc105",     // model
    "",          // variant
    "ctrl:nocaps", // options
)
Systemd Services
// System services
fest.SystemService("docker", "sshd")
fest.SystemTimer("fstrim")
fest.SystemSocket("docker")

// User services
fest.Service("syncthing")
fest.Timer("backup")
fest.Socket("pipewire")
User Groups
fest.Group("docker", "wheel", "audio", "video")
System Files

Place files in a system/ directory mirroring their target paths:

system/
  etc/
    hosts
    pacman.conf
  usr/
    local/
      bin/
        myscript
// Automatically discovers and manages files in system/
fest.SystemFilesDir("system")
Dotfiles with GNU Stow

Place your dotfiles in user/ directory:

user/
  .config/
    nvim/
      init.vim
  .bashrc
  .vimrc

Dotfiles are automatically managed when using apply or save commands.

Lifecycle Hooks

Execute custom code before or after resource synchronization:

// Run after docker is installed
fest.After(fest.ResourcePackages, func() {
    // Custom setup logic
})

// Run before applying configuration
fest.OnCommand(fest.PhaseBeforeApply, func() {
    // Pre-apply checks
})

Architecture

Two-Phase Execution
  1. Configuration Phase: Builds lists of desired state by executing your Go code
  2. Synchronization Phase: Compares current state with desired state and applies changes
Package Manager Interface

All resource types implement the same interface:

type packageManager interface {
    ResourceName() string
    Wanted() []string
    Match(want, have string) bool
    ListInstalled() ([]string, error)
    ListExplicit() ([]string, error)
    Install(pkgs []string) error
    Uninstall(pkgs []string) error
    MarkExplicit(pkgs []string) error
    GetDependencies() (map[string][]string, error)
    SaveAsGo(wanted []string) error
}
State Tracking
  • Pacman packages: Uses pacman's built-in explicit/dependency tracking
  • System files: Maintains state in ~/.local/share/dotfiles/system-files-state.json
  • Dotfiles: Managed via GNU Stow
  • Services: Tracked via systemd's enabled/disabled state

Advanced Topics

Modular Configuration

Organize your configuration into multiple files:

mysystem/
  main.go           # Entry point
  packages.go       # Package declarations
  services.go       # Service declarations
  system.go         # System configuration

Each file can have init() functions that declare resources:

// packages.go
package main

import "github.com/emad-elsaid/fest"

func init() {
    fest.Package("vim", "git")
}
Machine-Specific Configuration

Use build tags or environment variables for machine-specific config:

// +build workstation

package main

import "github.com/emad-elsaid/fest"

func init() {
    fest.Package("docker", "kubectl")
}
Custom Dependencies

Add custom dependency checks:

fest.OnCommand(fest.PhaseBeforeApply, func() {
    // Custom validation logic
})

Troubleshooting

Dependency Errors

If you see dependency errors, ensure all required tools are installed:

go run . diff  # Will check and attempt to install missing dependencies
Permission Issues

Some operations require sudo. The tool will prompt for sudo password when needed.

State Conflicts

If the state file becomes corrupted:

rm ~/.local/share/dotfiles/system-files-state.json
go run . apply  # Rebuilds state

Comparison to Other Tools

Feature fest Ansible NixOS
Language Go YAML Nix
Type Safety
Compilation
Arch-Specific
Requires New Distro
Learning Curve Low Medium High

Contributing

Contributions are welcome! This framework is designed to be extended with new package managers and resource types.

To add a new resource manager:

  1. Implement the packageManager interface
  2. Add it to allManagers() in main.go
  3. Create public functions for users to declare resources

Migration from Ruby Version

If you're migrating from the original Ruby implementation:

  1. The API is very similar but uses Go syntax instead of Ruby
  2. Replace require statements with import
  3. Replace do blocks with func init() functions
  4. The command structure is the same (apply, save, diff)

License

This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.

This project incorporates code from yay which is also licensed under GPL v3.

Documentation

Overview

Package fest provides a declarative system configuration management framework for Arch Linux.

It allows you to declare your system state (packages, services, files) and synchronizes the actual system state to match. The package supports various resource types including pacman packages, flatpak apps, npm packages, Go packages, Ruby gems, systemd services, user groups, system files, and dotfile management via GNU Stow.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func After

func After(resourceName ResourceName, callback Callback)

After registers a callback to be executed after the given resource name is synced.

func Before

func Before(resourceName ResourceName, callback Callback)

Before registers a callback to be executed before the given resource name is synced.

func Flatpak

func Flatpak(apps ...string)

Flatpak declares desired flatpak applications (by application ID). Applications are installed from Flathub by default.

Example:

managers.Flatpak(
    "com.slack.Slack",
    "com.github.tchx84.Flatseal",
    "org.mozilla.firefox",
)

func GoPackage

func GoPackage(pkgs ...string)

GoPackage declares Go packages to install using `go install`. Packages are installed to GOBIN or $GOPATH/bin. Supports version pinning using @version syntax.

Example:

fest.GoPackage(
    "github.com/golangci/golangci-lint/cmd/golangci-lint@latest",
    "golang.org/x/tools/cmd/goimports@v0.15.0",
)

func Group

func Group(grps ...string)

Group declares user groups that the current user should be a member of. The user is added to these groups using usermod -aG.

Example:

fest.Group("docker", "wheel", "audio", "video")

func Keyboard

func Keyboard(keymap, layout, model, variant, options string)

Keyboard sets keyboard configuration for both console and X11. Parameters:

  • keymap: Console keymap (e.g., "us")
  • layout: X11 keyboard layout (e.g., "us,ara" for multiple layouts)
  • model: Keyboard model (e.g., "pc105")
  • variant: Layout variant (e.g., "dvorak")
  • options: Keyboard options (e.g., "ctrl:nocaps,grp:alt_shift_toggle")

Example:

fest.Keyboard("us", "us", "pc105", "", "ctrl:nocaps")

func Locale

func Locale(locale string)

Locale sets the system locale. The locale string should match an entry in /etc/locale.gen.

Example:

fest.Locale("en_US.UTF-8 UTF-8")

func Main

func Main()

Main is the entry point for the archlinux configuration management system. It handles command-line argument parsing and dispatches to the appropriate command handler.

Available commands:

  • apply: Synchronize system state with declared configuration
  • save: Save current system state as declarative Go code
  • diff: Show what would change without making actual changes

This function should be called from your main package after declaring your desired configuration.

func NpmPackage

func NpmPackage(pkgs ...string)

NpmPackage declares global npm packages to install. Supports version pinning using @version syntax. Handles scoped packages correctly (e.g., @vue/cli).

Example:

fest.NpmPackage(
    "typescript",
    "@vue/cli",
    "eslint@8.50.0",  // Pin to specific version
)

func OnCommand

func OnCommand(phase CommandPhase, callback Callback)

OnCommand registers a callback to be executed during the given command phase.

func Package

func Package(pkgs ...string)

Package declares one or more pacman packages to be installed. Packages can be from official repositories or AUR.

Example:

fest.Package("vim", "git", "docker")

func PackageGroup

func PackageGroup(groupNames ...string)

PackageGroup expands pacman package groups into individual packages. This queries pacman for all packages in the given groups and adds them.

Example:

fest.PackageGroup("base-devel")

func RubyGem

func RubyGem(gems ...string)

RubyGem declares Ruby gems to install for the current user. Supports version pinning and version constraints:

  • gem@1.0.0: Exact version
  • gem@~>1.0: Pessimistic version constraint (>= 1.0, < 2.0)
  • gem@>=1.0.0: Minimum version

Example:

fest.RubyGem(
    "bundler",
    "rails@7.0.0",
    "puma@~>5.0",
)

func Service

func Service(svcs ...string)

Service declares user-level systemd services to enable. Services run as the current user.

Example:

fest.Service("syncthing", "ssh-agent")

func Socket

func Socket(socks ...string)

Socket declares user-level systemd sockets to enable.

Example:

fest.Socket("pipewire")

func SystemFilesDir

func SystemFilesDir(dir string)

SystemFilesDir adds a directory to scan for system files to deploy. Files in this directory are copied to the system, mirroring the directory structure. For example, a file at "system/etc/hosts" will be copied to "/etc/hosts".

The framework tracks changes and can restore original files when uninstalling.

Example:

fest.SystemFilesDir("system")  // Default
fest.SystemFilesDir("custom-system-files")  // Additional directory

func SystemService

func SystemService(svcs ...string)

SystemService declares system-level systemd services to enable. Services are enabled and started on apply.

Example:

fest.SystemService("docker", "sshd")

func SystemSocket

func SystemSocket(socks ...string)

SystemSocket declares system-level systemd sockets to enable.

Example:

fest.SystemSocket("docker")

func SystemTimer

func SystemTimer(tmrs ...string)

SystemTimer declares system-level systemd timers to enable.

Example:

fest.SystemTimer("fstrim")

func Timedate

func Timedate(timezone string, ntp bool)

Timedate sets timezone and NTP configuration. This configures the system timezone and enables/disables NTP time synchronization.

Example:

fest.Timedate("America/New_York", true)  // Set timezone and enable NTP

func Timer

func Timer(tmrs ...string)

Timer declares user-level systemd timers to enable.

Example:

fest.Timer("backup")

Types

type Callback

type Callback func()

Callback is a function that can be executed before or after a package manager operation.

type CommandPhase

type CommandPhase string

CommandPhase represents a phase in the command lifecycle.

const (
	PhaseBeforeDiff  CommandPhase = "before-diff"
	PhaseAfterDiff   CommandPhase = "after-diff"
	PhaseBeforeSave  CommandPhase = "before-save"
	PhaseAfterSave   CommandPhase = "after-save"
	PhaseBeforeApply CommandPhase = "before-apply"
	PhaseAfterApply  CommandPhase = "after-apply"
)

Available command lifecycle phases.

type ResourceName

type ResourceName string

ResourceName represents a package manager resource type identifier.

const (
	ResourceSystemServices ResourceName = "system services"
	ResourceSystemTimers   ResourceName = "system timers"
	ResourceSystemSockets  ResourceName = "system sockets"
	ResourceUserServices   ResourceName = "user services"
	ResourceUserTimers     ResourceName = "user timers"
	ResourceUserSockets    ResourceName = "user sockets"
)
const ResourceBrokenSymlinks ResourceName = "broken symlinks"
const ResourceFlatpak ResourceName = "flatpak"
const ResourceGoPackages ResourceName = "Go packages"
const ResourceNpmPackages ResourceName = "npm packages"
const ResourcePackages ResourceName = "packages"

ResourcePackages is the resource name for pacman packages.

const ResourceRubyGems ResourceName = "Ruby gems"
const ResourceUserGroups ResourceName = "user groups"

Directories

Path Synopsis
yay
Experimental code for install local with dependency refactoring Not at feature parity with install.go
Experimental code for install local with dependency refactoring Not at feature parity with install.go
pkg/cmd/graph command
pkg/menus
Clean Build Menu functions
Clean Build Menu functions

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL