Skip to content

Initializers

Initializers are responsible for setting up the CMDR environment during the cmdr init command. They configure the filesystem, shell profiles, and other necessary components.

Initializer Interface

The core interface is simple1:

type Initializer interface {
    Init(isUpgrade bool) error
}

The isUpgrade parameter indicates whether this is a fresh installation or an upgrade of an existing installation.

Factory Registration

Initializers register themselves via factory pattern2:

core.RegisterInitializerFactory("filesystem", func(cfg Configuration) (Initializer, error) {
    return NewFilesystemInitializer(cfg), nil
})

Initializer Implementations

Filesystem Initializers

Source: core/initializer/filesystem.go

FSBackup

Backs up existing profile directory before modifications3:

type FSBackup struct {
    path   string  // Directory to backup
    target string  // Backup location
}

Usage: Registered as "profile-dir-backup"

EmbedFSExporter

Exports embedded filesystem to disk4:

type EmbedFSExporter struct {
    filesystem fs.FS       // Embedded filesystem
    srcPath    string      // Source path in embed
    dstPath    string      // Destination path on disk
    fileMode   os.FileMode // File permissions
}

Usage: Registered as "profile-dir-export" - exports shell profile scripts from embedded assets.

DirRender

Renders Go templates in a directory5:

type DirRender struct {
    data    interface{} // Template data
    srcPath string      // Directory to render
    ext     string      // Template extension (e.g., ".gotmpl")
}

Usage: Registered as "profile-dir-render" - renders profile templates with configuration.

Profile Initializer

Source: core/initializer/profile.go

ProfileInjector

Injects CMDR initialization script into shell profile6:

type ProfileInjector struct {
    scriptPath  string // Path to cmdr_initializer.sh
    profilePath string // Path to shell profile (~/.bashrc, ~/.zshrc, etc.)
}

Key Operations:

  1. Detect Shell Profile7:
  2. Bash → ~/.bashrc
  3. Zsh → ~/.zshrc
  4. Fish → ~/.config/fish/config.fish
  5. Ash/Sh → ~/.profile

  6. Generate Profile Statement:

    source '/path/to/.cmdr/profile/cmdr_initializer.sh'
    

  7. Update Profile:

  8. Reads existing profile
  9. Replaces old CMDR source line (if exists)
  10. Adds source line (if not exists)
  11. Writes updated profile

Shell Detection: Uses parent process inspection to determine the current shell.

Command Initializer

Source: core/initializer/command.go

Registers CMDR itself as a managed command, enabling cmdr upgrade.

Database Initializer

Source: core/initializer/database.go

Initializes the BoltDB database schema.

Initialization Flow

When cmdr init is run, initializers execute in this order:

1. profile-dir-backup
   └─> Backup existing profile directory

2. binary (BinaryManager.Init)
   └─> Create bin/ and shims/ directories

3. profile-dir-export
   └─> Export embedded profile scripts

4. profile-dir-render
   └─> Render profile templates with config

5. profile-injector
   └─> Add source line to shell profile

6. database
   └─> Initialize database schema

7. command
   └─> Register cmdr as managed command

Embedded Assets

CMDR embeds shell profile scripts using Go 1.16+ embed:

//go:embed embed/*
var EmbedFS embed.FS

The embedded profile directory contains:

embed/
└── profile/
    ├── cmdr_initializer.sh.gotmpl  # Shell initializer template
    └── ...

These are extracted to ~/.cmdr/profile/ and rendered with configuration values.

Template Rendering

Templates use Go's text/template package with configuration data:

// Example template
// cmdr_initializer.sh.gotmpl
export CMDR_ROOT="{{.Configuration.GetString "core.root_dir"}}"
export PATH="{{.Configuration.GetString "core.bin_dir"}}:$PATH"

Rendered output:

export CMDR_ROOT="/home/user/.cmdr"
export PATH="/home/user/.cmdr/bin:$PATH"

Safety Features

  1. Backup Before Modify: Profile directory is backed up before changes
  2. Idempotent Injection: Multiple cmdr init calls don't duplicate source lines
  3. Version Migration: Old format source lines are updated to new format
  4. Error Recovery: Failures during init don't leave system in broken state

Testing Initializers

Initializers can be tested independently:

// Example test
cfg := core.NewConfiguration()
cfg.Set(core.CfgKeyCmdrProfileDir, "/tmp/test-profile")

injector := NewProfileInjector("/tmp/test-profile/init.sh", "/tmp/.bashrc")
err := injector.Init(false)


  1. Initializer interface in core/initializer.go L5-L7 

  2. Factory registration pattern in core/initializer.go L18-L20 

  3. FSBackup implementation in core/initializer/filesystem.go L21-L61 

  4. EmbedFSExporter implementation in core/initializer/filesystem.go L63-L146 

  5. DirRender implementation in core/initializer/filesystem.go L148-L264 

  6. ProfileInjector implementation in core/initializer/profile.go L20-L118 

  7. Shell detection in core/initializer/profile.go L120-L151