Borg/rfc/RFC-008-BORGFILE.md

5.7 KiB

RFC-008: Borgfile Compilation

Status: Draft Author: Snider Created: 2026-01-13 License: EUPL-1.2 Depends On: RFC-003, RFC-004


Abstract

Borgfile is a declarative syntax for defining TIM container contents. It specifies how local files are mapped into the container filesystem, enabling reproducible container builds.

1. Overview

Borgfile provides:

  • Dockerfile-like syntax for familiarity
  • File mapping into containers
  • Simple ADD directive
  • Integration with TIM encryption

2. File Format

2.1 Location

  • Default: Borgfile in current directory
  • Override: borg compile -f path/to/Borgfile

2.2 Encoding

  • UTF-8 text
  • Unix line endings (LF)
  • No BOM

3. Syntax

3.1 Parsing Implementation

// cmd/compile.go:33-54
lines := strings.Split(content, "\n")
for _, line := range lines {
    parts := strings.Fields(line)  // Whitespace-separated tokens
    if len(parts) == 0 {
        continue  // Skip empty lines
    }
    switch parts[0] {
    case "ADD":
        // Process ADD directive
    default:
        return fmt.Errorf("unknown instruction: %s", parts[0])
    }
}

3.2 ADD Directive

ADD <source> <destination>
Parameter Description
source Local path (relative to current working directory)
destination Container path (leading slash stripped)

3.3 Examples

# Add single file
ADD ./app /usr/local/bin/app

# Add configuration
ADD ./config.yaml /etc/myapp/config.yaml

# Multiple files
ADD ./bin/server /app/server
ADD ./static /app/static

4. Path Resolution

4.1 Source Paths

  • Resolved relative to current working directory (not Borgfile location)
  • Must exist at compile time
  • Read via os.ReadFile(src)

4.2 Destination Paths

  • Leading slash stripped: strings.TrimPrefix(dest, "/")
  • Added to DataNode as-is
// cmd/compile.go:46-50
data, err := os.ReadFile(src)
if err != nil {
    return fmt.Errorf("invalid ADD instruction: %s", line)
}
name := strings.TrimPrefix(dest, "/")
m.RootFS.AddData(name, data)

5. File Handling

5.1 Permissions

Current implementation: Permissions are NOT preserved.

Source Container
Any file 0600 (hardcoded in DataNode.ToTar)
Any directory 0755 (implicit)

5.2 Timestamps

  • Set to time.Now() when added to DataNode
  • Original timestamps not preserved

5.3 File Types

  • Regular files only
  • No directory recursion (each file must be added explicitly)
  • No symlink following

6. Error Handling

Error Cause
invalid ADD instruction: {line} Wrong number of arguments
os.ReadFile error Source file not found
unknown instruction: {name} Unrecognized directive
ErrPasswordRequired Encryption requested without password

7. CLI Flags

// cmd/compile.go:80-82
-f, --file     string  Path to Borgfile (default: "Borgfile")
-o, --output   string  Output path (default: "a.tim")
-e, --encrypt  string  Password for .stim encryption (optional)

8. Output Formats

8.1 Plain TIM

borg compile -f Borgfile -o container.tim

Output: Standard TIM tar archive with config.json + rootfs/

8.2 Encrypted STIM

borg compile -f Borgfile -e "password" -o container.stim

Output: ChaCha20-Poly1305 encrypted STIM container

Auto-detection: If -e flag provided, output automatically uses .stim format even if -o specifies .tim.

9. Default OCI Config

The current implementation creates a minimal config:

// pkg/tim/config.go:6-10
func defaultConfig() (*trix.Trix, error) {
    return &trix.Trix{Header: make(map[string]interface{})}, nil
}

Note: This is a placeholder. For full OCI runtime execution, you'll need to provide a proper config.json in the container or modify the TIM after compilation.

10. Compilation Process

1. Read Borgfile content
2. Parse line-by-line
3. For each ADD directive:
   a. Read source file from filesystem
   b. Strip leading slash from destination
   c. Add to DataNode
4. Create TIM with default config + populated RootFS
5. If password provided:
   a. Encrypt to STIM via ToSigil()
   b. Adjust output extension to .stim
6. Write output file

11. Implementation Reference

  • Parser/Compiler: cmd/compile.go
  • TIM creation: pkg/tim/tim.go
  • DataNode: pkg/datanode/datanode.go
  • Tests: cmd/compile_test.go

12. Current Limitations

Feature Status
Comment support (#) Not implemented
Quoted paths Not implemented
Directory recursion Not implemented
Permission preservation Not implemented
Path resolution relative to Borgfile Not implemented (uses CWD)
Full OCI config generation Not implemented (empty header)
Symlink following Not implemented

13. Examples

13.1 Simple Application

ADD ./myapp /usr/local/bin/myapp
ADD ./config.yaml /etc/myapp/config.yaml

13.2 Web Application

ADD ./server /app/server
ADD ./index.html /app/static/index.html
ADD ./style.css /app/static/style.css
ADD ./app.js /app/static/app.js

13.3 With Encryption

# Create Borgfile
cat > Borgfile << 'EOF'
ADD ./secret-app /app/secret-app
ADD ./credentials.json /etc/app/credentials.json
EOF

# Compile with encryption
borg compile -f Borgfile -e "MySecretPassword123" -o secret.stim

14. Future Work

  • Comment support (#)
  • Quoted path support for spaces
  • Directory recursion in ADD
  • Permission preservation
  • Path resolution relative to Borgfile location
  • Full OCI config generation
  • Variable substitution (${VAR})
  • Include directive
  • Glob patterns in source
  • COPY directive (alias for ADD)