blackship

Crates.ioblackship
lib.rsblackship
version0.1.4
created_at2025-12-24 22:19:01.073429+00
updated_at2026-01-25 17:03:52.345948+00
descriptionA FreeBSD jail manager with state machine control
homepage
repositoryhttps://github.com/seuros/blackship
max_upload_size
id2003927
size518,594
Abdelkader Boudih (seuros)

documentation

README

Blackship

A FreeBSD jail orchestrator with TOML configuration, dependency management, state machine lifecycle control, and ZFS integration.

Features

  • Declarative Configuration: Define jails in TOML with dependencies, networking, and hooks
  • Dependency Management: Automatically start/stop jails in correct order using dependency graph
  • State Machine Lifecycle: Clean state transitions (stopped → starting → running → stopping)
  • VNET Networking: Bridge-based networking with epair interfaces, static IPs, and MAC addresses
  • ZFS Integration: Automatic dataset creation, snapshots, clones, and efficient export/import
  • Jailfile Templates: Docker-like build system for reproducible jail creation
  • Health Checks: HTTP, TCP, and command-based health monitoring
  • Lifecycle Hooks: Run scripts at start/stop with configurable failure handling
  • Port Forwarding: PF-based port exposure with source IP binding
  • Shell Completion: Bash, Zsh, and Fish completions

Requirements

  • FreeBSD 15.0+ with jail support
  • ZFS (optional, for snapshots/clones)
  • PF (optional, for port forwarding)

Installation

# Install from crates.io
cargo install blackship

# Or download precompiled binary from releases
fetch https://github.com/seuros/blackship/releases/latest/download/blackship-freebsd-amd64.tar.gz
tar xzf blackship-freebsd-amd64.tar.gz
sudo mv blackship /usr/local/bin/

# Install shell completion (optional)
blackship completion bash > /usr/local/etc/bash_completion.d/blackship
blackship completion zsh > /usr/local/share/zsh/site-functions/_blackship
blackship completion fish > ~/.config/fish/completions/blackship.fish

Default Paths

Blackship uses XDG-compliant user directories by default:

Path Default Environment Override
data_dir ~/.local/share/blackship XDG_DATA_HOME
releases_dir ~/.local/share/blackship/releases -
cache_dir ~/.cache/blackship XDG_CACHE_HOME

These defaults allow running blackship without root for most operations. Override in blackship.toml for system-wide paths:

[config]
data_dir = "/var/blackship"
releases_dir = "/var/blackship/releases"
cache_dir = "/var/blackship/cache"

Note: While paths are user-writable by default, bootstrap extraction requires root privileges because FreeBSD base archives contain root-owned files with special permissions (setuid binaries, etc.). Use sudo blackship bootstrap <release> for extraction.

Quick Start

1. Initialize Jailfile

# Create a Jailfile in current directory
blackship init

# Create with specific FreeBSD release
blackship init --release 15.0-RELEASE

# TOML format instead of Dockerfile-like
blackship init --toml

2. Initialize Armada Configuration

Create blackship.toml:

[config]
# Paths default to XDG directories (~/.local/share/blackship, ~/.cache/blackship)
# Uncomment to use system-wide paths:
# data_dir = "/var/blackship"
zfs_enabled = true
zpool = "zroot"
dataset = "blackship"

[[jails]]
name = "web"
release = "15.0-RELEASE"
hostname = "web.local"

[jails.network]
vnet = true
bridge = "blackship0"
ip = "10.0.1.10"
gateway = "10.0.1.1"

Or use blackship armada init to generate a template.

3. Bootstrap a Release

# Download and extract FreeBSD base (requires root for extraction)
sudo blackship bootstrap 15.0-RELEASE

# Dry run to see what would be downloaded
blackship bootstrap 15.0-RELEASE --dry-run

# List available releases
blackship releases

4. Create Network

# Create bridge with gateway
blackship network create default --subnet 10.0.1.0/24 --gateway 10.0.1.1 --bridge blackship0

5. Start Jails

# Start a specific jail
blackship up web

# Start all jails
blackship up --all

# Dry run (show what would happen)
blackship up --all --dry-run

6. Interact with Jails

# Open console
blackship console web

# Execute command
blackship exec web -- pkg install -y nginx

# Check status
blackship ps
blackship ps --json

Configuration Reference

Global Config

[config]
# All paths are optional - defaults to XDG directories
data_dir = "~/.local/share/blackship"      # Base data directory (default)
releases_dir = "~/.local/share/blackship/releases"  # FreeBSD releases (default)
cache_dir = "~/.cache/blackship"           # Download cache (default)
mirror_url = "https://download.freebsd.org"  # FreeBSD mirror
zfs_enabled = true                         # Enable ZFS features
zpool = "zroot"                            # ZFS pool name
dataset = "blackship"                      # Base dataset name

Jail Definition

[[jails]]
name = "myapp"                        # Jail name (required)
release = "15.0-RELEASE"              # Base release
path = "/jails/myapp"                 # Custom path (optional)
hostname = "myapp.local"              # Hostname
depends_on = ["database"]             # Dependencies

[jails.network]
vnet = true                           # Enable VNET
bridge = "blackship0"                 # Bridge interface
ip = "10.0.1.10"                      # Static IP
gateway = "10.0.1.1"                  # Default gateway
mac_address = "02:00:00:00:00:01"     # Static MAC (optional)

[jails.network.dns]
nameservers = ["8.8.8.8", "8.8.4.4"]  # DNS servers
mode = "custom"                        # custom or inherit

[jails.healthcheck]
enabled = true

[[jails.healthcheck.checks]]
name = "http"
command = "curl -sf http://localhost:80/health"
target = "jail"
interval = 30
timeout = 10
retries = 3

[[jails.healthcheck.checks]]
name = "process"
command = "pgrep nginx"
target = "jail"

[[jails.hooks]]
phase = "post_start"
target = "jail"
command = "/etc/rc.d/nginx start"
on_failure = "abort"                  # abort or continue

[[jails.hooks]]
phase = "pre_stop"
target = "jail"
command = "/etc/rc.d/nginx stop"
on_failure = "continue"

Commands

Lifecycle

Command Description
blackship up [jail] [--all] [--dry-run] Start jail(s) with dependencies
blackship down [jail] [--all] [--dry-run] Stop jail(s) in reverse order
blackship restart [jail] [--all] [--dry-run] Restart jail(s)
blackship ps [--json] List jail status
blackship check Validate configuration
blackship setup Initialize PF firewall anchor
blackship cleanup <jail> [--force] Clean up failed jail resources
blackship init [-f file] [--release] [--toml] Create a new Jailfile

Console & Execution

Command Description
blackship console <jail> [-u user] Open interactive shell
blackship exec <jail> [-u user] [-w dir] [-e K=V] -- <cmd> Execute command in jail
blackship run --name <n> --release <r> [-d] -- <cmd> Run ephemeral jail (auto-cleanup unless -d)

File Operations

Command Description
blackship cp <src> <dest> Copy files (use jail:path for jail paths)
blackship rm <jails...> [-f] [--volumes] Remove/destroy jails

Bootstrap & Releases

Command Description
blackship bootstrap <release> [-f] [-a archives] Download FreeBSD release
blackship releases [list|delete|verify] [--json] Manage releases

Networking

Command Description
blackship network create <name> -s <subnet> [-g gw] [-b bridge] Create network
blackship network destroy <name> [--force] Destroy network
blackship network list List networks
blackship expose <jail> -p <port> [-I bind-ip] [--proto tcp|udp] Expose port
blackship ports [jail] List exposed ports

Snapshots & Clones (requires ZFS)

Command Description
blackship snapshot create <jail> [name] Create snapshot
blackship snapshot list <jail> [--json] List snapshots
blackship snapshot rollback <jail> <snap> [--force] Rollback to snapshot
blackship snapshot delete <jail> <snap> Delete snapshot
blackship clone <jail>@<snap> <newname> Clone from snapshot

Export & Import

Command Description
blackship export <jail> [-o file] [--zfs-send] Export to archive
blackship import <file> [-n name] [--force] Import from archive

Build System

Command Description
blackship build [-f Jailfile] [-n name] [--build-arg K=V] [--dry-run] Build from Jailfile
blackship template list List templates
blackship template inspect <file> Show Jailfile details
blackship template validate <file> Validate Jailfile

Health & Monitoring

Command Description
blackship health [jail] [-w] [-i interval] [--json] Health check status
blackship supervise Start Warden supervisor for auto-restart
blackship logs <jail> [-f] [-n lines] Tail jail logs

Armada (Multi-Jail Orchestration)

Command Description
blackship armada init [-f file] Create a new blackship.toml
blackship armada up [-d] [jails...] Start all jails
blackship armada down [jails...] Stop all jails
blackship armada build [jails...] Build jails from Jailfiles
blackship armada ps [--json] Show status of all jails
blackship armada config [--show] Validate and show configuration

Config File Merging

Armada supports merging multiple config files (later files override earlier):

# Merge base config with production overrides
blackship armada -f base.toml -f prod.toml up

# Merge multiple files
blackship armada -f base.toml -f secrets.toml -f local.toml up

Jailfile References

Jails in blackship.toml can reference Jailfiles for building:

[[jails]]
name = "web"
build = "./web"              # Directory containing Jailfile
depends_on = ["db"]

[[jails]]
name = "api"
jailfile = "./custom/Api.jailfile"  # Explicit Jailfile path

Shell Completion

Command Description
blackship completion bash|zsh|fish Generate shell completion

Jailfile Format

Jailfiles define reproducible jail builds, similar to Dockerfiles:

# Jailfile
FROM 15.0-RELEASE

# Metadata
METADATA name=nginx-jail version=1.0

# Build arguments
ARG NGINX_VERSION=1.24

# Environment variables
ENV NGINX_VERSION=${NGINX_VERSION}

# Run commands (executed via jexec)
RUN pkg install -y nginx-${NGINX_VERSION}
RUN sysrc nginx_enable=YES

# Copy files from build context
COPY nginx.conf /usr/local/etc/nginx/nginx.conf
COPY html/ /usr/local/www/html/

# Set working directory
WORKDIR /usr/local/www

# Expose ports (documentation)
EXPOSE 80/tcp
EXPOSE 443/tcp

# Default command
CMD /usr/local/sbin/nginx -g 'daemon off;'

TOML Format (Alternative)

# Jailfile.toml
[metadata]
name = "nginx-jail"
version = "1.0"

from = "15.0-RELEASE"

[[args]]
name = "NGINX_VERSION"
default = "1.24"

[[instructions]]
type = "run"
command = "pkg install -y nginx"

[[instructions]]
type = "copy"
src = "nginx.conf"
dest = "/usr/local/etc/nginx/nginx.conf"

[[expose]]
port = 80
protocol = "tcp"

cmd = "/usr/local/sbin/nginx -g 'daemon off;'"

Build Commands

# Basic build
blackship build -f Jailfile -n myjail

# With build arguments
blackship build -f Jailfile -n myjail --build-arg NGINX_VERSION=1.26

# Dry run
blackship build -f Jailfile --dry-run

ZFS Integration

When zfs_enabled = true, Blackship:

  1. Creates datasets automatically: zpool/blackship/jails/<name>
  2. Enables snapshots and clones
  3. Supports ZFS send/receive for fast export/import

Snapshot Workflow

# Create snapshot before changes
blackship snapshot create web pre-update

# Make changes
blackship exec web -- pkg upgrade -y

# If something breaks, rollback
blackship down web
blackship snapshot rollback web pre-update --force
blackship up web

# Clone for testing
blackship clone web@pre-update web-test

Export/Import with ZFS

# Fast export using ZFS send
blackship export web -o web-backup.zfs --zfs-send

# Standard tar.zst export
blackship export web -o web-backup.tar.zst

# Import (auto-detects format)
blackship import web-backup.tar.zst --name web-restored

Networking

VNET Setup

Blackship uses VNET jails with epair interfaces connected to a bridge:

Host Bridge (blackship0)
├── epair0a ←→ epair0b (jail: web, 10.0.1.10)
├── epair1a ←→ epair1b (jail: db, 10.0.1.11)
└── gateway: 10.0.1.1

Port Forwarding

Uses PF anchors to avoid modifying /etc/pf.conf:

# Expose nginx on port 80
blackship expose web -p 80

# Expose on specific host IP
blackship expose web -p 443 -I 192.168.1.100

# Different internal port
blackship expose web -p 8080 --internal 80

Add to /etc/pf.conf:

rdr-anchor "blackship"
anchor "blackship"

Health Checks

All health checks are command-based (exit 0 = healthy).

HTTP Check

[[jails.healthcheck.checks]]
name = "api"
command = "curl -sf http://localhost:8080/health"
target = "jail"
interval = 30
timeout = 10

TCP Check

[[jails.healthcheck.checks]]
name = "postgres"
command = "nc -z localhost 5432"
target = "jail"

Command Check

[[jails.healthcheck.checks]]
name = "nginx-running"
command = "service nginx status"
target = "jail"

Monitoring

# One-time check
blackship health web

# Watch mode (updates every 5 seconds)
blackship health --watch --interval 5

# JSON output for scripting
blackship health --json

Dependencies

Jails start in dependency order and stop in reverse:

[[jails]]
name = "app"
depends_on = ["cache", "database"]

[[jails]]
name = "cache"

[[jails]]
name = "database"
# Starts: database → cache → app
blackship up app

# Stops: app → cache → database
blackship down app

Examples

Web Application Stack

[config]
# data_dir defaults to ~/.local/share/blackship
zfs_enabled = true
zpool = "zroot"
dataset = "blackship"

[[jails]]
name = "postgres"
release = "15.0-RELEASE"
hostname = "db.local"
[jails.network]
vnet = true
bridge = "blackship0"
ip = "10.0.1.10"
gateway = "10.0.1.1"

[[jails]]
name = "redis"
release = "15.0-RELEASE"
hostname = "cache.local"
[jails.network]
vnet = true
bridge = "blackship0"
ip = "10.0.1.11"
gateway = "10.0.1.1"

[[jails]]
name = "webapp"
release = "15.0-RELEASE"
hostname = "app.local"
depends_on = ["postgres", "redis"]
[jails.network]
vnet = true
bridge = "blackship0"
ip = "10.0.1.20"
gateway = "10.0.1.1"
[jails.network.dns]
nameservers = ["8.8.8.8"]
mode = "custom"

[jails.healthcheck]
enabled = true

[[jails.healthcheck.checks]]
name = "http"
command = "curl -sf http://localhost:3000/health"
target = "jail"
interval = 30

Backup and Migration

# Create snapshot
blackship snapshot create webapp v1.0

# Export for migration
blackship export webapp -o webapp-v1.0.tar.zst

# On new host
blackship import webapp-v1.0.tar.zst --name webapp
# Edit blackship.toml to add jail config
blackship up webapp

CI/CD Integration (Example)

This is an example of how Blackship could be used as a backend for CI/CD systems. This is not a built-in integration - it demonstrates how the run, exec, cp, and rm commands can be combined for CI/CD workflows.

Example: Gitea Actions with act_runner

Blackship could be used as a backend for Gitea Actions via act_runner.

Setup

  1. Bootstrap a release:
blackship bootstrap 15.0-RELEASE
  1. Configure act_runner with jail labels:
runner:
  labels:
    - "freebsd-15:jail://15.0-RELEASE"
  1. Workflows targeting runs-on: freebsd-15 would execute in ephemeral jails.

How it Would Work

When act_runner receives a job with a jail:// label, the runner could:

  1. Create an ephemeral jail via blackship run --name gitea-runner-<id> --release <release> --detach
  2. Clone the repository into the jail using blackship cp
  3. Execute each step via blackship exec <jail> --workdir /workspace -- <command>
  4. Clean up via blackship rm <jail> --force

Note: This requires custom act_runner configuration or a wrapper script - it is not built into act_runner by default.

Troubleshooting

Jail won't start

# Check configuration
blackship check

# Try dry run
blackship up myjail --dry-run

# Clean up failed resources
blackship cleanup myjail --force

Network issues

# List bridges
blackship network list

# Check jail IP
blackship exec myjail -- ifconfig

# Verify routing
blackship exec myjail -- netstat -rn

ZFS issues

# Verify dataset exists
zfs list | grep blackship

# Check snapshots
blackship snapshot list myjail

# Manual cleanup
zfs destroy -r zroot/blackship/jails/myjail

License

BSD-3-Clause - See LICENSE file for details.

Author

Abdelkader Boudih oss@seuros.com

Commit count: 13

cargo fmt