CLI Package Documentation
May 13, 2026 ยท View on GitHub
Overview
The cli package handles command-line interface, configuration management, and Docker label-based job discovery.
Key Components
Configuration Management
Config Structure
Central configuration holder for all job types and global settings.
type Config struct {
ExecJobs map[string]*ExecJobConfig
RunJobs map[string]*RunJobConfig
LocalJobs map[string]*LocalJobConfig
ServiceJobs map[string]*ServiceJobConfig
ComposeJobs map[string]*ComposeJobConfig
Global GlobalConfig
Middlewares map[string]map[string]interface{}
}
GlobalConfig
System-wide settings and defaults.
type GlobalConfig struct {
SlackURL string
SlackChannel string
EmailFrom string
EmailTo string
SMTPHost string
SMTPPort int
SMTPUser string
SMTPPassword string
SaveFolder string
// Docker settings
DockerHost string
DockerPollInterval time.Duration
DockerEvents bool
AllowHostJobsFromLabels bool
// Web UI settings
EnableWeb bool
WebAddress string
// Monitoring
EnablePprof bool
PprofAddress string
}
Configuration Sources
INI File Configuration
Traditional file-based configuration.
func BuildFromFile(path string) (*Config, error) {
config := &Config{}
cfg, err := ini.LoadSources(ini.LoadOptions{}, path)
// Parse sections and build jobs
return config, nil
}
INI Format:
[global]
slack-webhook = https://hooks.slack.com/...
docker-events = true
[job-exec "database-backup"]
schedule = @midnight
container = postgres
command = pg_dump mydb > /backup/db.sql
[job-run "cleanup"]
schedule = 0 2 * * *
image = alpine:latest
command = find /tmp -mtime +7 -delete
Docker Labels Configuration
Dynamic configuration from container labels.
func BuildFromDockerContainers(client *docker.Client) (*Config, error) {
containers, err := client.ListContainers()
for _, container := range containers {
labels := container.Labels
// Parse ofelia.* labels
// Create job configurations
}
return config, nil
}
Label Format:
labels:
ofelia.enabled: "true"
ofelia.job-exec.backup.schedule: "0 2 * * *"
ofelia.job-exec.backup.command: "backup.sh"
ofelia.job-exec.backup.user: "root"
Configuration Operations
Merging Configurations
Combines multiple configuration sources.
func (c *Config) mergeConfig(parsedConfig *Config) {
// Merge global settings
mergeGlobalConfig(&c.Global, &parsedConfig.Global)
// Merge job collections
for name, job := range parsedConfig.ExecJobs {
c.ExecJobs[name] = job
}
// ... merge other job types
}
Hash-based Change Detection
Detects configuration changes for dynamic updates.
func (c *Config) dockerContainersUpdate(client *docker.Client) error {
newConfig, err := BuildFromDockerContainers(client)
for name, newJob := range newConfig.ExecJobs {
if oldJob, exists := c.ExecJobs[name]; exists {
oldHash, _ := oldJob.Hash()
newHash, _ := newJob.Hash()
if oldHash != newHash {
// Update job configuration
}
}
}
return nil
}
Commands
Daemon Command
Main scheduler daemon.
func DaemonCommand(c *cli.Context) error {
config := loadConfig(c)
scheduler := core.NewScheduler(logger)
// Add jobs to scheduler
for _, job := range config.GetJobs() {
scheduler.AddJob(job)
}
// Start monitoring
if config.Global.DockerEvents {
monitor := core.NewContainerMonitor(client, scheduler)
go monitor.Start()
}
// Start scheduler
scheduler.Start()
// Wait for shutdown
<-stopSignal
scheduler.Stop()
}
Validate Command
Configuration validation without execution.
func ValidateCommand(c *cli.Context) error {
config, err := loadConfig(c)
if err != nil {
return fmt.Errorf("invalid configuration: %w", err)
}
validator := config2.NewConfigValidator(config)
if err := validator.Validate(); err != nil {
return fmt.Errorf("validation failed: %w", err)
}
fmt.Println("Configuration is valid")
return nil
}
Docker Integration
Label Parsing
Extracts job configuration from Docker labels.
func parseJobLabels(labels map[string]string) map[string]JobConfig {
jobs := make(map[string]JobConfig)
for label, value := range labels {
if !strings.HasPrefix(label, "ofelia.") {
continue
}
parts := strings.Split(label, ".")
// ofelia.job-type.job-name.property
jobType := parts[1]
jobName := parts[2]
property := parts[3]
job := getOrCreateJob(jobs, jobType, jobName)
setJobProperty(job, property, value)
}
return jobs
}
Security Validation
Ensures label-based jobs meet security requirements.
func validateLabelJob(job JobConfig, global GlobalConfig) error {
// Check if host jobs are allowed
if job.Type == "local" && !global.AllowHostJobsFromLabels {
return errors.New("host jobs not allowed from labels")
}
// Validate command injection
if containsShellMetacharacters(job.Command) {
return errors.New("potentially unsafe command")
}
return nil
}
Usage Examples
Loading Configuration
// From INI file
config, err := BuildFromFile("/etc/ofelia/config.ini")
// From Docker labels
client := docker.NewClient("unix:///var/run/docker.sock")
config, err := BuildFromDockerContainers(client)
// Merged configuration
fileConfig, _ := BuildFromFile("config.ini")
labelConfig, _ := BuildFromDockerContainers(client)
fileConfig.mergeConfig(labelConfig)
Dynamic Updates
// Monitor for configuration changes
ticker := time.NewTicker(30 * time.Second)
for range ticker.C {
if err := config.dockerContainersUpdate(client); err != nil {
log.Printf("Update failed: %v", err)
}
}
Custom Job Creation
job := &RunJobConfig{
BareJobConfig: BareJobConfig{
Schedule: "@hourly",
Command: "process-data",
},
Image: "myapp:latest",
Environment: []string{"ENV=production"},
Volumes: []string{"/data:/data:ro"},
}
config.RunJobs["data-processor"] = job
Configuration Precedence
- Command-line flags (highest priority)
- Environment variables
- Configuration file
- Docker labels (lowest priority)
Testing
The package includes tests for:
- Configuration parsing
- Label extraction
- Job validation
- Hash-based change detection
- Security validation
Best Practices
- Use INI files for static configuration
- Use Docker labels for dynamic, container-specific jobs
- Enable
docker-eventsfor real-time updates - Validate configuration before deployment
- Use environment variables for sensitive data
See also: Core Package | Web Package | Project Index