deb-osbuild/docs/debos.md
robojerk 544eb61951 docs: Reorganize documentation into proper docs/ directory structure
- Moved all documentation files to docs/ directory for better organization
- Maintained all existing documentation content
- Improved project structure for better maintainability
- Documentation now follows standard open source project layout
2025-08-12 00:27:41 -07:00

1355 lines
37 KiB
Markdown

# debos Comprehensive Top-to-Bottom Analysis
## Overview
debos is a Debian OS image builder that creates reproducible, customized operating system images through a YAML-based recipe system. It operates by executing a series of actions in a controlled virtual machine environment, ensuring consistency across different host systems. This document provides a complete top-to-bottom analysis of the entire debos process, from recipe parsing to final artifact generation.
## Table of Contents
1. [Complete Process Flow](#complete-process-flow)
2. [Core Architecture](#core-architecture)
3. [Recipe Processing Pipeline](#recipe-processing-pipeline)
4. [Action System Deep Dive](#action-system-deep-dive)
5. [Fakemachine Integration](#fakemachine-integration)
6. [Command Execution System](#command-execution-system)
7. [Filesystem Operations](#filesystem-operations)
8. [Archive Management](#archive-management)
9. [External Tools and Dependencies](#external-tools-and-dependencies)
10. [Context Management](#context-management)
11. [Error Handling and Debugging](#error-handling-and-debugging)
12. [Performance Characteristics](#performance-characteristics)
13. [Integration Points](#integration-points)
14. [Complete Workflow Examples](#complete-workflow-examples)
## Complete Process Flow
### End-to-End Workflow
```
1. Recipe Loading → 2. Template Processing → 3. YAML Parsing → 4. Action Validation → 5. Fakemachine Setup → 6. Action Execution → 7. Artifact Generation → 8. Cleanup
```
### Detailed Process Steps
#### **Phase 1: Recipe Preparation**
1. **File Loading**: Read YAML recipe file from disk
2. **Template Processing**: Apply Go templates with variables
3. **YAML Parsing**: Parse processed YAML into Recipe struct
4. **Action Mapping**: Map YAML actions to concrete Action implementations
5. **Validation**: Verify all action parameters and dependencies
#### **Phase 2: Environment Setup**
1. **Fakemachine Creation**: Initialize virtual machine backend
2. **Resource Allocation**: Set CPU, memory, scratch space
3. **Volume Mounting**: Mount recipe directory, artifact directory
4. **Environment Variables**: Propagate proxy and custom variables
5. **Architecture Setup**: Configure cross-architecture support
#### **Phase 3: Action Execution**
1. **Pre-Machine Setup**: Run PreMachine hooks for all actions
2. **Sequential Execution**: Execute actions in recipe order
3. **Chroot Management**: Handle filesystem isolation
4. **Command Execution**: Run external tools and scripts
5. **State Management**: Maintain context across actions
#### **Phase 4: Artifact Generation**
1. **Output Collection**: Gather results from all actions
2. **Post-Processing**: Run PostMachine hooks
3. **File Generation**: Create final artifacts (images, archives)
4. **Cleanup**: Remove temporary files and mounts
## Core Architecture
### Design Philosophy
debos follows a **pipeline-based architecture** where:
- **Recipes** define the build process as a sequence of actions
- **Actions** are self-contained, independent modules
- **Fakemachine** provides isolated execution environment
- **Context** maintains state across the entire build process
### Key Components
```
debos CLI → Recipe Parser → Action Executor → Fakemachine → Output
↓ ↓ ↓ ↓ ↓
Main Entry YAML/YAML+Go Action Runner VM Backend Artifacts
```
## Recipe Processing Pipeline
### Complete Recipe Processing Flow
#### **1. Template Processing Engine**
```go
func (r *Recipe) Parse(file string, printRecipe bool, dump bool, templateVars ...map[string]string) error {
t := template.New(path.Base(file))
funcs := template.FuncMap{
"sector": sector, // Add 's' suffix for sector calculations
"escape": escape, // Shell escape variables
"uuid5": uuid5, // Generate deterministic UUIDs
}
t.Funcs(funcs)
// Add slim-sprig functions for advanced templating
t.Funcs(sprig.FuncMap())
// Parse and execute template
if _, err := t.ParseFiles(file); err != nil {
return err
}
data := new(bytes.Buffer)
if err := t.Execute(data, templateVars[0]); err != nil {
return err
}
// Unmarshal processed YAML
if err := yaml.Unmarshal(data.Bytes(), &r); err != nil {
return err
}
return nil
}
```
#### **2. Action Factory System**
```go
func (y *YamlAction) UnmarshalYAML(unmarshal func(interface{}) error) error {
var aux debos.BaseAction
err := unmarshal(&aux)
if err != nil {
return err
}
// Factory pattern for action creation
switch aux.Action {
case "debootstrap":
y.Action = NewDebootstrapAction()
case "mmdebstrap":
y.Action = NewMmdebstrapAction()
case "apt":
y.Action = NewAptAction()
case "run":
y.Action = &RunAction{}
case "image-partition":
y.Action = &ImagePartitionAction{}
case "overlay":
y.Action = &OverlayAction{}
case "pack":
y.Action = NewPackAction()
// ... more actions
default:
return fmt.Errorf("Unknown action: %v", aux.Action)
}
// Unmarshal action-specific options
err = unmarshal(y.Action)
return err
}
```
#### **3. Template Functions Available**
- **`sector(s int) string`**: Adds 's' suffix for sector calculations
- **`escape(s string) string`**: Shell-escapes variables for safe command execution
- **`uuid5(namespace, data) string`**: Generates deterministic UUIDs using SHA1
- **slim-sprig functions**: Advanced string manipulation, math, crypto functions
#### **4. Recipe Validation Process**
```go
func (r *Recipe) Parse(file string, printRecipe bool, dump bool, templateVars ...map[string]string) error {
// ... template processing ...
// Mandatory field validation
if len(r.Architecture) == 0 {
return fmt.Errorf("Recipe file must have 'architecture' property")
}
if len(r.Actions) == 0 {
return fmt.Errorf("Recipe file must have at least one action")
}
// Set defaults
if r.SectorSize == 0 {
r.SectorSize = 512
}
return nil
}
```
### Recipe Structure and Syntax
#### **Basic Recipe Format**
```yaml
{{- $image := or .image "debian.tgz" -}}
{{- $suite := or .suite "bookworm" -}}
architecture: {{ .architecture }}
sectorsize: 512
actions:
- action: debootstrap
suite: {{ $suite }}
mirror: https://deb.debian.org/debian
variant: minbase
- action: apt
packages: [ sudo, openssh-server, systemd-sysv ]
- action: run
chroot: true
script: scripts/setup-system.sh
- action: pack
file: {{ $image }}
compression: gz
```
#### **Template Variable Sources**
1. **Command Line**: `debos -t variable:value recipe.yaml`
2. **Environment**: System environment variables
3. **Built-in**: Architecture, suite defaults
4. **Recipe**: Local variable definitions with `{{- $var := "value" -}}`
## Execution Flow
### 1. CLI Entry Point (`cmd/debos/debos.go`)
The main entry point handles:
- Command-line argument parsing
- Environment variable propagation
- Fakemachine backend selection
- Recipe file validation
```go
func main() {
context := debos.DebosContext{...}
// Parse command line options
parser := flags.NewParser(&options, flags.Default)
// Create fakemachine or run on host
if !options.DisableFakeMachine {
m, err = fakemachine.NewMachineWithBackend(options.Backend)
// Execute in VM
} else {
// Execute on host
}
}
```
### 2. Recipe Parsing (`actions/recipe.go`)
Recipes are parsed with Go template support:
- **YAML parsing** with `gopkg.in/yaml.v2`
- **Go template expansion** for dynamic values
- **Variable substitution** with `-t` command line options
- **Validation** of action parameters
### 3. Action Execution Pipeline
Actions are executed sequentially with lifecycle hooks:
```go
type Action interface {
Verify(context *DebosContext) error // Pre-execution validation
PreMachine(context *DebosContext, m *fakemachine.Machine, args *[]string) error
PreNoMachine(context *DebosContext) error // Host-side preparation
Run(context *DebosContext) error // Main execution
Cleanup(context *DebosContext) error // Per-action cleanup
PostMachine(context *DebosContext) error // Post-execution processing
PostMachineCleanup(context *DebosContext) error // Host-side cleanup
}
```
## Action System Deep Dive
### Action Types
debos provides 20+ built-in actions:
#### **System Construction Actions**
- `debootstrap`: Create base filesystem using debootstrap
- `mmdebstrap`: Alternative to debootstrap with better performance
- `pacstrap`: Arch Linux system construction
- `apt`: Package installation and management
#### **Filesystem Actions**
- `overlay`: Copy files/directories to target filesystem
- `filesystem-deploy`: Deploy rootfs to image partitions
- `unpack`: Extract archives to filesystem
#### **Image Creation Actions**
- `image-partition`: Create partitioned disk images
- `raw`: Write raw data to specific offsets
- `ostree-commit`: Create OSTree commits
- `ostree-deploy`: Deploy OSTree branches
#### **Utility Actions**
- `run`: Execute commands/scripts (chroot or host)
- `download`: Download files from network
- `pack`: Create compressed archives
### Action Implementation Pattern
Each action follows a consistent pattern:
```go
type ExampleAction struct {
debos.BaseAction `yaml:",inline"`
// Action-specific fields
Parameter1 string `yaml:"parameter1"`
Parameter2 bool `yaml:"parameter2"`
}
func (a *ExampleAction) Verify(context *debos.DebosContext) error {
// Validate parameters
return nil
}
func (a *ExampleAction) PreMachine(context *debos.DebosContext, m *fakemachine.Machine, args *[]string) error {
// Prepare fakemachine environment
return nil
}
func (a *ExampleAction) PreNoMachine(context *debos.DebosContext) error {
// Prepare host environment
return nil
}
func (a *ExampleAction) Run(context *debos.DebosContext) error {
// Execute action logic
return nil
}
func (a *ExampleAction) Cleanup(context *debos.DebosContext) error {
// Clean up action resources
return nil
}
func (a *ExampleAction) PostMachine(context *debos.DebosContext) error {
// Post-execution processing
return nil
}
func (a *ExampleAction) PostMachineCleanup(context *debos.DebosContext) error {
// Host-side cleanup
return nil
}
```
### Action Lifecycle Management
#### **Execution Phases**
1. **Verify**: Validate action parameters and dependencies
2. **PreMachine/PreNoMachine**: Prepare execution environment
3. **Run**: Execute main action logic
4. **Cleanup**: Clean up action-specific resources
5. **PostMachine**: Post-execution processing
6. **PostMachineCleanup**: Host-side cleanup
#### **Resource Management**
- **Deferred Cleanup**: Ensures cleanup runs even on errors
- **Stacked Cleanup**: Multiple cleanup methods execute in reverse order
- **Context Persistence**: State maintained across action boundaries
### Key Actions Deep Dive
#### **debootstrap Action** (`actions/debootstrap_action.go`)
**Purpose**: Create base Debian filesystem
**Implementation**: Wraps `debootstrap` command with additional features
```go
type DebootstrapAction struct {
Suite string
Mirror string
Variant string
Components []string
MergedUsr bool `yaml:"merged-usr"`
CheckGpg bool `yaml:"check-gpg"`
}
func (d *DebootstrapAction) Run(context *debos.DebosContext) error {
// Build debootstrap command
cmd := debos.Command{}
cmd.AddEnvKey("DEBOOTSTRAP_DIR", "/usr/share/debootstrap")
// Execute with proper environment
return cmd.Run("debootstrap", args...)
}
```
**External Tools Used**:
- `debootstrap`: Core system construction tool
- `gpg`: GPG signature verification
- `wget/curl`: Package download
#### **image-partition Action** (`actions/image_partition_action.go`)
**Purpose**: Create partitioned disk images
**Implementation**: Comprehensive disk partitioning and filesystem creation
```go
type ImagePartitionAction struct {
ImageName string
ImageSize string
PartitionType string
Partitions []Partition
MountPoints []MountPoint
}
func (i *ImagePartitionAction) Run(context *debos.DebosContext) error {
// Create image file
// Partition using parted/sfdisk
// Format filesystems
// Mount partitions
// Update context with partition info
}
```
**External Tools Used**:
- `parted`: GPT/MBR partition table creation
- `sfdisk`: Alternative partitioning tool
- `mkfs.*`: Filesystem formatting tools
- `mount/umount`: Partition mounting
- `losetup`: Loop device management
#### **run Action** (`actions/run_action.go`)
**Purpose**: Execute custom commands/scripts
**Implementation**: Flexible execution with chroot support
```go
type RunAction struct {
Chroot bool
PostProcess bool
Script string
Command string
Label string
}
func (run *RunAction) doRun(context debos.DebosContext) error {
var cmd debos.Command
if run.Chroot {
cmd = debos.NewChrootCommandForContext(context)
}
// Execute with proper environment variables
return cmd.Run(label, cmdline...)
}
```
**External Tools Used**:
- `chroot`: Filesystem isolation
- `bash/sh`: Script execution
- `mount`: Bind mounts for script access
## Command Execution System
### Complete Command Execution Architecture
#### **Command Structure and Configuration**
```go
type Command struct {
Architecture string // Target architecture for cross-compilation
Dir string // Working directory for command execution
Chroot string // Chroot path for filesystem isolation
ChrootMethod ChrootEnterMethod // Method to enter chroot environment
bindMounts []string // Items to bind mount into chroot
extraEnv []string // Extra environment variables to set
}
type ChrootEnterMethod int
const (
CHROOT_METHOD_NONE = iota // No chroot isolation
CHROOT_METHOD_NSPAWN // Use systemd-nspawn for containerization
CHROOT_METHOD_CHROOT // Use traditional chroot
)
```
#### **Command Execution Flow**
```go
func (cmd Command) Run(label string, cmdline ...string) error {
// 1. Setup QEMU helper for cross-architecture support
q, err := newQemuHelper(cmd)
if err != nil {
return err
}
q.Setup()
defer q.Cleanup()
// 2. Build command options based on chroot method
var options []string
switch cmd.ChrootMethod {
case CHROOT_METHOD_NONE:
options = cmdline
case CHROOT_METHOD_CHROOT:
options = append(options, "chroot", cmd.Chroot)
options = append(options, cmdline...)
case CHROOT_METHOD_NSPAWN:
options = buildNspawnCommand(cmd, cmdline)
}
// 3. Execute command with proper environment
exe := exec.Command(options[0], options[1:]...)
w := newCommandWrapper(label)
exe.Stdout = w
exe.Stderr = w
// 4. Handle resolv.conf management
resolvsum, err := cmd.saveResolvConf()
if err != nil {
return err
}
// 5. Execute and cleanup
if err = exe.Run(); err != nil {
return err
}
return cmd.restoreResolvConf(resolvsum)
}
```
#### **Chroot Method Implementation**
**Traditional chroot**:
```go
case CHROOT_METHOD_CHROOT:
options = append(options, "chroot")
options = append(options, cmd.Chroot)
options = append(options, cmdline...)
```
**systemd-nspawn**:
```go
case CHROOT_METHOD_NSPAWN:
options = append(options, "systemd-nspawn", "-q")
options = append(options, "--resolv-conf=off")
options = append(options, "--timezone=off")
options = append(options, "--register=no")
options = append(options, "--keep-unit")
options = append(options, "--console=pipe")
// Add environment variables
for _, e := range cmd.extraEnv {
options = append(options, "--setenv", e)
}
// Add bind mounts
for _, b := range cmd.bindMounts {
options = append(options, "--bind", b)
}
options = append(options, "-D", cmd.Chroot)
options = append(options, cmdline...)
```
#### **Cross-Architecture Support**
```go
func newQemuHelper(c Command) (*qemuHelper, error) {
q := qemuHelper{}
if c.Chroot == "" || c.Architecture == "" {
return &q, nil
}
// Map architectures to QEMU binaries
switch c.Architecture {
case "armhf", "armel", "arm":
if runtime.GOARCH != "arm64" && runtime.GOARCH != "arm" {
q.qemusrc = "/usr/bin/qemu-arm-static"
}
case "arm64":
if runtime.GOARCH != "arm64" {
q.qemusrc = "/usr/bin/qemu-aarch64-static"
}
case "i386":
if runtime.GOARCH != "amd64" && runtime.GOARCH != "386" {
q.qemusrc = "/usr/bin/qemu-i386-static"
}
case "amd64":
if runtime.GOARCH != "amd64" {
q.qemusrc = "/usr/bin/qemu-x86_64-static"
}
// ... more architectures
}
if q.qemusrc != "" {
q.qemutarget = path.Join(c.Chroot, q.qemusrc)
}
return &q, nil
}
```
#### **Environment Variable Management**
```go
func (cmd *Command) AddEnv(env string) {
cmd.extraEnv = append(cmd.extraEnv, env)
}
func (cmd *Command) AddEnvKey(key, value string) {
cmd.extraEnv = append(cmd.extraEnv, fmt.Sprintf("%s=%s", key, value))
}
func (cmd *Command) AddBindMount(source, target string) {
var mount string
if target != "" {
mount = fmt.Sprintf("%s:%s", source, target)
} else {
mount = source
}
cmd.bindMounts = append(cmd.bindMounts, mount)
}
```
#### **Service Management in Chroot**
```go
// Disable services start/stop for commands running in chroot
if cmd.ChrootMethod != CHROOT_METHOD_NONE {
services := ServiceHelper{cmd.Chroot}
services.Deny()
defer services.Allow()
}
```
#### **resolv.conf Handling**
```go
func (cmd *Command) saveResolvConf() (*[sha256.Size]byte, error) {
hostconf := "/etc/resolv.conf"
chrootedconf := path.Join(cmd.Chroot, hostconf)
savedconf := chrootedconf + ".debos"
// Save original resolv.conf
if _, err := os.Lstat(chrootedconf); !os.IsNotExist(err) {
if err = os.Rename(chrootedconf, savedconf); err != nil {
return nil, err
}
}
// Copy host resolv.conf to chroot
data, err := ioutil.ReadFile(hostconf)
if err != nil {
return nil, err
}
out := []byte("# Automatically generated by Debos\n")
out = append(out, data...)
sum := sha256.Sum256(out)
err = ioutil.WriteFile(chrootedconf, out, 0644)
return &sum, err
}
```
### Command Output Handling
#### **Output Wrapper System**
```go
type commandWrapper struct {
label string
buffer *bytes.Buffer
}
func newCommandWrapper(label string) *commandWrapper {
b := bytes.Buffer{}
return &commandWrapper{label, &b}
}
func (w commandWrapper) out(atEOF bool) {
for {
s, err := w.buffer.ReadString('\n')
if err == nil {
log.Printf("%s | %v", w.label, s)
} else {
if len(s) > 0 {
if atEOF && err == io.EOF {
log.Printf("%s | %v\n", w.label, s)
} else {
w.buffer.WriteString(s)
}
}
break
}
}
}
func (w *commandWrapper) Write(p []byte) (n int, err error) {
n, err = w.buffer.Write(p)
w.out(false)
return
}
```
## Fakemachine Integration
### Purpose and Benefits
Fakemachine provides:
- **Isolation**: Build environment independent of host
- **Reproducibility**: Consistent results across different hosts
- **Safety**: Prevents host system contamination
- **Performance**: Optimized for build workloads
### Backend Selection
```go
// Automatic backend selection
m, err = fakemachine.NewMachineWithBackend("auto")
// Manual backend selection
m, err = fakemachine.NewMachineWithBackend("kvm") // Fastest
m, err = fakemachine.NewMachineWithBackend("uml") // Medium
m, err = fakemachine.NewMachineWithBackend("qemu") // Slowest
```
### Backend Performance Comparison
| Backend | Performance | Requirements | Use Case |
|---------|-------------|--------------|----------|
| `kvm` | ⭐⭐⭐⭐⭐ | `/dev/kvm` access | Production builds |
| `uml` | ⭐⭐⭐ | `user-mode-linux` package | Development/testing |
| `qemu` | ⭐⭐ | None | Fallback, compatibility |
| `--disable-fakemachine` | ⭐⭐⭐⭐ | Root permissions | Debugging, simple builds |
### Machine Configuration
```go
// Memory configuration
memsize, err := units.RAMInBytes(options.Memory)
m.SetMemory(int(memsize / 1024 / 1024))
// CPU configuration
m.SetNumCPUs(options.CPUs)
// Scratch space
m.SetScratch(size, "")
// Environment variables
m.SetEnviron(EnvironString)
```
## Filesystem Operations
### Complete Filesystem Management System
#### **Path Management Functions**
```go
func CleanPathAt(path, at string) string {
if filepath.IsAbs(path) {
return filepath.Clean(path)
}
return filepath.Join(at, path)
}
func CleanPath(path string) string {
cwd, _ := os.Getwd()
return CleanPathAt(path, cwd)
}
func RealPath(path string) (string, error) {
p, err := filepath.EvalSymlinks(path)
if err != nil {
return "", err
}
return filepath.Abs(p)
}
func RestrictedPath(prefix, dest string) (string, error) {
var err error
destination := path.Join(prefix, dest)
destination, err = filepath.Abs(destination)
if err != nil {
return "", err
}
if !strings.HasPrefix(destination, prefix) {
return "", fmt.Errorf("The resulting path points outside of prefix '%s': '%s'\n", prefix, destination)
}
return destination, nil
}
```
#### **File Copying Operations**
```go
func CopyFile(src, dst string, mode os.FileMode) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
// Create temporary file
tmp, err := ioutil.TempFile(filepath.Dir(dst), "")
if err != nil {
return err
}
// Copy content
_, err = io.Copy(tmp, in)
if err != nil {
tmp.Close()
os.Remove(tmp.Name())
return err
}
// Set permissions and close
if err = tmp.Close(); err != nil {
os.Remove(tmp.Name())
return err
}
if err = os.Chmod(tmp.Name(), mode); err != nil {
os.Remove(tmp.Name())
return err
}
// Atomic rename
if err = os.Rename(tmp.Name(), dst); err != nil {
os.Remove(tmp.Name())
return err
}
return nil
}
```
#### **Tree Copying Operations**
```go
func CopyTree(sourcetree, desttree string) error {
walker := func(p string, info os.FileInfo, err error) error {
if err != nil {
return err
}
suffix, _ := filepath.Rel(sourcetree, p)
target := path.Join(desttree, suffix)
switch info.Mode() & os.ModeType {
case 0: // Regular file
err := CopyFile(p, target, info.Mode())
if err != nil {
return fmt.Errorf("Failed to copy file %s: %w", p, err)
}
case os.ModeDir: // Directory
os.Mkdir(target, info.Mode())
case os.ModeSymlink: // Symlink
link, err := os.Readlink(p)
if err != nil {
return fmt.Errorf("Failed to read symlink %s: %w", suffix, err)
}
os.Symlink(link, target)
default:
return fmt.Errorf("File %s with mode %v not handled", p, info.Mode())
}
return nil
}
return filepath.Walk(sourcetree, walker)
}
```
#### **Filesystem Safety Features**
- **Atomic Operations**: Use temporary files and atomic renames
- **Path Validation**: Prevent directory traversal attacks
- **Permission Preservation**: Maintain original file modes
- **Symlink Handling**: Properly handle symbolic links
- **Error Recovery**: Clean up temporary files on failure
## Archive Management
### Complete Archive Processing System
#### **Archive Type Support**
```go
type ArchiveType int
const (
_ ArchiveType = iota // Guess from file extension
Tar
Zip
Deb
)
type ArchiveBase struct {
file string // Path to archive file
atype ArchiveType
options map[interface{}]interface{} // Archiver-specific options
}
```
#### **Archive Unpacking Operations**
```go
func (tar *ArchiveTar) Unpack(destination string) error {
command := []string{"tar"}
usePigz := false
// Determine compression type and options
if compression, ok := tar.options["compression"].(string); ok {
if compression == "gz" {
// Check if pigz is available for parallel decompression
if _, err := exec.LookPath("pigz"); err == nil {
usePigz = true
command = append(command, "--use-compress-program=pigz")
} else {
command = append(command, "--gzip")
}
} else if opts := tarOptions(compression); opts != "" {
command = append(command, opts)
}
}
command = append(command, "-xf", tar.file, "-C", destination)
return unpack(command, destination)
}
```
#### **Compression Support**
```go
func tarOptions(compression string) string {
unpackTarOpts := map[string]string{
"bzip2": "--bzip2",
"gz": "--gzip",
"lzip": "--lzip",
"lzma": "--lzma",
"lzop": "--lzop",
"xz": "--xz",
"zstd": "--zstd",
}
return unpackTarOpts[compression]
}
```
#### **Archive Management Features**
- **Multiple Formats**: Support for tar, zip, and deb packages
- **Compression Detection**: Automatic compression type detection
- **Parallel Processing**: Use pigz for faster gzip decompression
- **Error Handling**: Robust error handling and cleanup
- **Option Support**: Configurable unpacking options
## External Tools and Dependencies
### Core System Tools
#### **Filesystem Management**
- `debootstrap`: Base system construction
- `mmdebstrap`: Alternative system construction
- `parted`: Partition table management
- `sfdisk`: Scriptable partitioning
- `mkfs.ext4`, `mkfs.xfs`, `mkfs.fat`: Filesystem creation
- `mount`, `umount`: Filesystem mounting
- `losetup`: Loop device management
#### **Package Management**
- `apt-get`, `apt`: Debian package management
- `pacman`: Arch Linux package management
- `pacstrap`: Arch Linux system construction
#### **Compression and Archiving**
- `tar`: Archive creation and extraction
- `gzip`, `bzip2`, `xz`: Compression
- `cpio`: Archive format support
#### **Network Tools**
- `wget`, `curl`: File download
- `rsync`: Synchronization
### Build System Dependencies
#### **Go Dependencies** (`go.mod`)
```go
require (
github.com/go-debos/fakemachine v0.0.11 // VM backend
github.com/docker/go-units v0.5.0 // Human-readable sizes
github.com/freddierice/go-losetup/v2 v2.0.1 // Loop device management
github.com/sjoerdsimons/ostree-go v0.0.0 // OSTree support
gopkg.in/yaml.v2 v2.4.0 // YAML parsing
)
```
#### **System Dependencies**
- `golang`: Go runtime and compiler
- `libglib2.0-dev`: GLib development files
- `libostree-dev`: OSTree development files
- `qemu-system-x86`: QEMU emulation
- `qemu-user-static`: User-mode QEMU
- `debootstrap`: Base system construction
- `systemd-container`: Container support
### External Tool Integration Points
#### **Command Execution**
```go
type Command struct {
// Command configuration
}
func (c *Command) Run(label string, args ...string) error {
// Execute with proper environment
// Handle output and errors
// Support chroot execution
}
```
#### **Chroot Support**
```go
func NewChrootCommandForContext(context DebosContext) Command {
cmd := Command{}
// Add bind mounts
// Set working directory
// Configure environment
return cmd
}
```
## Recipe Processing
### YAML + Go Template System
Recipes support dynamic content through Go templates:
```yaml
{{- $image := or .image "debian.tgz" -}}
{{- $suite := or .suite "bookworm" -}}
architecture: {{ .architecture }}
actions:
- action: debootstrap
suite: {{ $suite }}
mirror: https://deb.debian.org/debian
```
### Template Variable Sources
1. **Command Line**: `-t variable:value`
2. **Environment**: System environment variables
3. **Built-in**: Architecture, suite defaults
4. **Recipe**: Local variable definitions
### Recipe Validation
```go
func (r *Recipe) Parse(file string, printRecipe, verbose bool, templateVars map[string]string) error {
// Load and parse YAML
// Apply Go templates
// Validate action parameters
// Check dependencies
}
```
## Context Management
### DebosContext Structure
```go
type DebosContext struct {
*CommonContext
RecipeDir string
Architecture string
SectorSize int
}
type CommonContext struct {
Scratchdir string // Temporary working directory
Rootdir string // Target filesystem root
Artifactdir string // Output directory
Downloaddir string // Download cache
Image string // Current image file
ImagePartitions []Partition // Partition information
ImageMntDir string // Image mount point
ImageFSTab bytes.Buffer // Generated fstab
ImageKernelRoot string // Kernel root parameter
DebugShell string // Debug shell path
Origins map[string]string // Path mappings
State DebosState // Build state
EnvironVars map[string]string // Environment variables
PrintRecipe bool // Print final recipe
Verbose bool // Verbose output
}
```
### Origin System
The origin system provides path mapping for actions:
```go
context.Origins = map[string]string{
"artifacts": context.Artifactdir, // Output directory
"filesystem": context.Rootdir, // Target filesystem
"recipe": context.RecipeDir, // Recipe directory
}
```
Actions can reference these origins:
```yaml
- action: overlay
source: overlays/config
destination: {{ .origin.filesystem }}/etc/config
```
## Error Handling and Debugging
### Error States
```go
type DebosState int
const (
Success DebosState = iota
Failed
)
```
### Debug Shell Integration
```go
if options.DebugShell {
context.DebugShell = options.Shell
// Fall into interactive shell on error
}
```
### Error Handling Flow
```go
func handleError(context *debos.DebosContext, err error, a debos.Action, stage string) bool {
if err == nil {
return false
}
context.State = debos.Failed
log.Printf("Action `%s` failed at stage %s, error: %s", a, stage, err)
if context.DebugShell != "" {
debos.DebugShell(*context)
}
return true
}
```
### Cleanup Mechanisms
Actions implement cleanup hooks:
- **Cleanup**: Per-action cleanup in same environment
- **PostMachineCleanup**: Host-side cleanup for all actions
- **Deferred execution**: Ensures cleanup runs even on errors
## Performance Characteristics
### Build Time Benchmarks
Based on [pine-a64-plus/debian.yaml](https://github.com/go-debos/debos-recipes/blob/9a25b4be6c9136f4a27e542f39ab7e419fc852c9/pine-a64-plus/debian.yaml) on Intel Pentium G4560T with SSD:
| Backend | Wall Time | Prerequisites |
|---------|-----------|---------------|
| `--disable-fakemachine` | 8 min | Root permissions |
| `-b kvm` | 9 min | Access to `/dev/kvm` |
| `-b uml` | 18 min | `user-mode-linux` package |
| `-b qemu` | 166 min | None |
### Performance Optimization
#### **Scratch Space Management**
```go
if options.ScratchSize != "" {
size, err := units.FromHumanSize(options.ScratchSize)
scratchsizeMB := int(size / 1000 / 1000)
m.SetScratch(size, "")
}
```
#### **Memory Configuration**
```go
if memsizeMB < 256 {
log.Printf("WARNING: Memory size of %dMB is less than recommended minimum 256MB\n", memsizeMB)
}
```
#### **CPU Allocation**
```go
if options.CPUs == 0 {
options.CPUs = 2 // Default to 2 CPUs
}
m.SetNumCPUs(options.CPUs)
```
## Integration Points
### Container Integration
#### **Docker Support**
```bash
# Official container
docker pull godebos/debos
# Usage
docker run --rm --privileged -v $(pwd):/workspace godebos/debos recipe.yaml
```
#### **Systemd-nspawn**
Fakemachine can use systemd-nspawn for containerization.
### CI/CD Integration
#### **Environment Variables**
```bash
# Proxy support
http_proxy, https_proxy, ftp_proxy, rsync_proxy, all_proxy, no_proxy
# Custom variables
debos -e BUILD_ID:123 -e BRANCH:main recipe.yaml
```
#### **Artifact Management**
```bash
# Output directory
debos --artifactdir ./outputs recipe.yaml
# Export specific artifacts
debos --export image --export archive recipe.yaml
```
### External Tool Integration
#### **OSTree Support**
```yaml
- action: ostree-commit
repository: {{ .origin.artifacts }}/ostree
parent: {{ .parent_commit }}
- action: ostree-deploy
repository: {{ .origin.artifacts }}/ostree
branch: {{ .branch }}
```
#### **Bootloader Integration**
debos doesn't have built-in bootloader actions but provides infrastructure:
```yaml
- action: run
chroot: true
script: scripts/install-grub.sh
- action: run
chroot: true
script: scripts/configure-bootloader.sh
```
## Advanced Features
### Multi-Architecture Support
```yaml
architecture: arm64
actions:
- action: debootstrap
suite: bookworm
# Architecture-specific packages
packages: [firmware-linux]
```
### Custom Action Development
#### **Action Interface Implementation**
```go
type CustomAction struct {
debos.BaseAction `yaml:",inline"`
CustomField string `yaml:"custom-field"`
}
func (a *CustomAction) Verify(context *debos.DebosContext) error {
// Validation logic
return nil
}
func (a *CustomAction) Run(context *debos.DebosContext) error {
// Execution logic
return nil
}
```
#### **Integration**
```yaml
- action: custom
custom-field: value
```
### Network and Proxy Support
#### **Automatic Proxy Detection**
```go
var environ_vars = [...]string {
"http_proxy", "https_proxy", "ftp_proxy",
"rsync_proxy", "all_proxy", "no_proxy",
}
```
#### **Proxy Validation**
```go
func warnLocalhost(variable string, value string) {
if strings.Contains(value, "localhost") ||
strings.Contains(value, "127.0.0.1") ||
strings.Contains(value, "::1") {
log.Printf("WARNING: Environment variable %s contains localhost", variable)
}
}
```
## Conclusion
debos provides a powerful, flexible framework for creating Debian-based operating system images. Its architecture emphasizes:
1. **Reproducibility**: Consistent results through isolated execution
2. **Flexibility**: Customizable through actions and scripts
3. **Performance**: Optimized backends for different use cases
4. **Integration**: Easy integration with existing toolchains
5. **Maintainability**: Clear separation of concerns and lifecycle hooks
The system's strength lies in its ability to combine declarative recipe definitions with imperative script execution, making it suitable for both simple image creation and complex, multi-stage build processes.
### Key Strengths
- **Isolation**: Fakemachine ensures build reproducibility
- **Extensibility**: Action system allows custom functionality
- **Performance**: Multiple backend options for different scenarios
- **Integration**: Works with existing Debian ecosystem tools
- **Cross-Architecture**: Full support for ARM, MIPS, RISC-V, etc.
- **Template System**: Advanced Go templating with custom functions
- **Command Execution**: Robust chroot and cross-architecture support
- **Filesystem Safety**: Atomic operations and path validation
### Areas for Enhancement
- **Bootloader Support**: No built-in bootloader management
- **Package Management**: Limited to Debian/Arch package managers
- **Image Formats**: Focus on filesystem and archive outputs
- **Validation**: Limited manifest validation capabilities
- **Parallel Execution**: Actions execute sequentially
- **Dependency Resolution**: No automatic dependency ordering
### Complete Process Summary
debos implements a **complete end-to-end image building pipeline** that:
1. **Processes Recipes**: YAML + Go templates with validation
2. **Manages Actions**: 20+ built-in actions with lifecycle hooks
3. **Executes Commands**: Robust chroot and cross-architecture support
4. **Handles Filesystems**: Safe file operations with atomic guarantees
5. **Manages Archives**: Multiple format support with compression
6. **Provides Isolation**: Fakemachine VM backends for reproducibility
7. **Supports Templates**: Advanced variable substitution and functions
8. **Generates Artifacts**: Images, archives, and OSTree repositories
The system's architecture emphasizes **reproducibility**, **flexibility**, and **integration** while maintaining **performance** and **security** through isolated execution environments.