Modules
Modules are the core building blocks of sysdef that define what packages, files, directories, and configurations should be managed on your system. Each module is a TypeScript file in the modules/ directory that exports a function returning a Module object.
Module Structure
Section titled “Module Structure”A module is defined by the Module interface in sysdef.ts:188:
export interface Module { readonly name: string; readonly variables: Record<string, string>; readonly packages: Record<string, string[]>; readonly directories: Record<string, string>; readonly files: Record<string, File>; readonly onEverySync?: (s: Shell) => Promise<void>;}Creating a Module
Section titled “Creating a Module”Module files must export a default function that takes a Shell function and returns a Module object:
import type { ModuleGenerator } from "../sysdef-src/sysdef";
const generator: ModuleGenerator = (shell) => { return { name: "example", variables: { "EXAMPLE_VAR": "example_value" }, packages: { "arch-official": ["firefox", "git"], "apt": ["curl", "wget"] }, directories: { "{HOMEDIR}/.config/example": "./config/example" }, files: { "{HOMEDIR}/.bashrc": "./dotfiles/bashrc", "{HOMEDIR}/.vimrc": (variables) => { return `" Generated vimrc for ${variables.get("USER")}\nset number\n`; } }, onEverySync: async (shell) => { await shell("echo 'Module synced!'", { displayOutput: true }); } };};
export default generator;Module Properties
Section titled “Module Properties”A unique identifier for your module. Used in logging and error messages.
variables
Section titled “variables”Key-value pairs that can be used in file paths and file content generation. These variables:
- Override global variables from
config.yamlwith the same name - Are scoped to this module only
- Can be referenced using
{VARIABLE_NAME}syntax in file paths
packages
Section titled “packages”Maps provider names to arrays of package specifications. Package specifications can be:
- Simple names:
"firefox"- uses any version (or lockfile version) - Versioned:
"firefox:115.0"- requires specific version - Complex versions:
"node:>=18.0.0"- version constraints (provider-dependent)
Example:
packages: { "apt": ["curl", "wget", "git:1:2.42.0-1"], "bun": ["typescript", "@types/node:20.8.0"]}directories
Section titled “directories”Maps destination paths to source paths for directory symlinks. Both paths support variable substitution:
directories: { "{HOMEDIR}/.config/nvim": "./config/nvim", "/etc/example": "./system-config"}During sync, sysdef creates symlinks from the destination to the source (resolved relative to your sysdef root directory).
Maps destination file paths to either:
- String paths (for symlinks):
"{HOMEDIR}/.bashrc": "./dotfiles/bashrc" - Generator functions (for generated content):
files: { "{HOMEDIR}/.gitconfig": (variables) => `[user] name = ${variables.get("GIT_USER_NAME")} email = ${variables.get("GIT_USER_EMAIL")}[core] editor = nvim`, "{HOMEDIR}/.profile": "./dotfiles/profile"}The File type is defined in sysdef.ts:185 as:
export type File = string | ((v: VariableStore) => string);onEverySync (optional)
Section titled “onEverySync (optional)”An async function that runs after files and packages are synced. Receives a Shell function for executing commands:
onEverySync: async (shell) => { // Restart a service after configuration changes await shell("systemctl --user restart example.service", { displayOutput: true });
// Run custom setup scripts await shell("bash ./scripts/post-sync.sh", { displayOutput: true });}Module Loading
Section titled “Module Loading”Modules are loaded by the loadModules() function in loaders.ts:8. The loading process:
- Scans the
modules/directory for files with valid extensions (.ts,.tsx,.js,.jsx) - Only loads modules listed in
config.yaml’smodulesarray - Dynamically imports each module file
- Calls the default exported function with the appropriate
Shellimplementation - Collects all returned
Moduleobjects
Variable Scoping
Section titled “Variable Scoping”Variables follow a hierarchical scoping system:
- Global variables from
config.yamlare available to all modules - Module variables override global variables within that module
- Variable substitution happens using
{VARIABLE_NAME}syntax - VariableStore (defined in
sysdef.ts:24) handles the substitution logic
Example with variable precedence:
variables: EDITOR: nano HOMEDIR: /home/userreturn { name: "dev", variables: { EDITOR: "nvim" // Overrides global EDITOR for this module }, files: { "{HOMEDIR}/.bashrc": (vars) => `export EDITOR=${vars.get("EDITOR")}` // Will use "nvim", not "nano" }};Package Version Management
Section titled “Package Version Management”Package versions are managed through:
- Explicit versions in module definitions
- Lockfile (
sysdef-lock.json) for reproducible builds - Version conflict detection - sysdef errors if modules request incompatible versions
The getPackageList() function in sysdef.ts:204 handles version resolution and conflict detection.
Error Handling
Section titled “Error Handling”Common module errors:
- Loading errors: Syntax errors, missing exports
- Version conflicts: Multiple modules requesting different versions of the same package
- Missing variables: Using undefined variables in file paths or content
- Invalid file paths: Malformed destination paths
All errors use the errorOut() function for consistent error reporting and process termination.