- 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
1355 lines
37 KiB
Markdown
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.
|