Complete infrastructure platform CLI: - Container runtime (systemd-nspawn) - VoltVisor VMs (Neutron Stardust / QEMU) - Stellarium CAS (content-addressed storage) - ORAS Registry - GitOps integration - Landlock LSM security - Compose orchestration - Mesh networking Copyright (c) Armored Gates LLC. All rights reserved. Licensed under AGPSL v5.0
423 lines
11 KiB
Bash
Executable File
423 lines
11 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# Volt Platform - Image Builder
|
|
# Creates TinyVol images from definitions
|
|
#
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
CONFIG_DIR="$PROJECT_DIR/configs/images"
|
|
OUTPUT_DIR="${OUTPUT_DIR:-/var/lib/volt/images}"
|
|
CACHE_DIR="${CACHE_DIR:-/var/cache/volt/packages}"
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
|
|
log() { echo -e "${GREEN}[volt]${NC} $1"; }
|
|
info() { echo -e "${BLUE}[volt]${NC} $1"; }
|
|
warn() { echo -e "${YELLOW}[volt]${NC} $1"; }
|
|
error() { echo -e "${RED}[volt]${NC} $1" >&2; }
|
|
|
|
# Base packages for each userland type
|
|
declare -A USERLAND_PACKAGES=(
|
|
["musl-minimal"]="musl busybox"
|
|
["glibc-standard"]="glibc bash coreutils util-linux systemd"
|
|
["busybox-tiny"]="busybox-static"
|
|
)
|
|
|
|
# Image definitions
|
|
declare -A IMAGES=(
|
|
["volt/server"]="server.yaml"
|
|
["volt/server-db-postgres"]="server-db-postgres.yaml"
|
|
["volt/dev"]="dev.yaml"
|
|
["volt/desktop-minimal"]="desktop-minimal.yaml"
|
|
["volt/desktop-productivity"]="desktop-productivity.yaml"
|
|
["volt/edge"]="edge.yaml"
|
|
["volt/k8s-node"]="k8s-node.yaml"
|
|
)
|
|
|
|
build_rootfs() {
|
|
local image_name="$1"
|
|
local config_file="$2"
|
|
local rootfs_dir="$3"
|
|
|
|
log "Building rootfs for: $image_name"
|
|
|
|
# Create directory structure
|
|
mkdir -p "$rootfs_dir"/{bin,sbin,usr/{bin,sbin,lib},lib,lib64,etc,var,tmp,proc,sys,dev,run,home,root,app}
|
|
|
|
# Parse YAML config (simplified - in production use proper YAML parser)
|
|
local userland=$(grep "userland:" "$config_file" 2>/dev/null | awk '{print $2}' || echo "musl-minimal")
|
|
|
|
info " Userland: $userland"
|
|
|
|
# Install base userland
|
|
case "$userland" in
|
|
musl-minimal)
|
|
install_musl_minimal "$rootfs_dir"
|
|
;;
|
|
glibc-standard)
|
|
install_glibc_standard "$rootfs_dir"
|
|
;;
|
|
busybox-tiny)
|
|
install_busybox_tiny "$rootfs_dir"
|
|
;;
|
|
*)
|
|
warn "Unknown userland: $userland, using musl-minimal"
|
|
install_musl_minimal "$rootfs_dir"
|
|
;;
|
|
esac
|
|
|
|
# Create essential files
|
|
create_essential_files "$rootfs_dir"
|
|
|
|
# Set permissions
|
|
chmod 1777 "$rootfs_dir/tmp"
|
|
chmod 755 "$rootfs_dir"
|
|
}
|
|
|
|
install_musl_minimal() {
|
|
local rootfs="$1"
|
|
|
|
info " Installing musl-minimal userland..."
|
|
|
|
# Download and install BusyBox static binary
|
|
local busybox_url="https://busybox.net/downloads/binaries/1.35.0-x86_64-linux-musl/busybox"
|
|
local busybox_path="$rootfs/bin/busybox"
|
|
|
|
if [[ ! -f "$CACHE_DIR/busybox" ]]; then
|
|
mkdir -p "$CACHE_DIR"
|
|
curl -fSL -o "$CACHE_DIR/busybox" "$busybox_url" || {
|
|
# Fallback: create minimal shell script
|
|
warn "Could not download busybox, creating minimal placeholder"
|
|
cat > "$busybox_path" << 'BUSYBOX'
|
|
#!/bin/sh
|
|
echo "Volt minimal shell"
|
|
exec /bin/sh "$@"
|
|
BUSYBOX
|
|
chmod +x "$busybox_path"
|
|
return 0
|
|
}
|
|
fi
|
|
|
|
cp "$CACHE_DIR/busybox" "$busybox_path"
|
|
chmod +x "$busybox_path"
|
|
|
|
# Create symlinks for common utilities
|
|
local utils="sh ash ls cat cp mv rm mkdir rmdir ln echo pwd env grep sed awk head tail sort uniq wc cut tr sleep date hostname uname id whoami ps kill"
|
|
for util in $utils; do
|
|
ln -sf busybox "$rootfs/bin/$util"
|
|
done
|
|
|
|
# Create sbin links
|
|
local sbin_utils="init halt reboot poweroff mount umount ifconfig route"
|
|
for util in $sbin_utils; do
|
|
ln -sf ../bin/busybox "$rootfs/sbin/$util"
|
|
done
|
|
}
|
|
|
|
install_glibc_standard() {
|
|
local rootfs="$1"
|
|
|
|
info " Installing glibc-standard userland..."
|
|
|
|
# For now, use Alpine as a base (it's actually musl but good enough for development)
|
|
# In production, this would pull from ArmoredGateHub registry
|
|
|
|
# Create minimal glibc-like structure
|
|
install_musl_minimal "$rootfs"
|
|
|
|
# Add bash if available
|
|
if command -v bash &>/dev/null; then
|
|
cp "$(command -v bash)" "$rootfs/bin/bash" 2>/dev/null || true
|
|
fi
|
|
|
|
# Copy essential libraries from host (for development only)
|
|
# In production, these come from TinyVol images
|
|
for lib in /lib/x86_64-linux-gnu/libc.so.6 /lib/x86_64-linux-gnu/libm.so.6 /lib/x86_64-linux-gnu/libdl.so.2 /lib/x86_64-linux-gnu/libpthread.so.0; do
|
|
if [[ -f "$lib" ]]; then
|
|
mkdir -p "$rootfs/lib/x86_64-linux-gnu"
|
|
cp "$lib" "$rootfs/lib/x86_64-linux-gnu/" 2>/dev/null || true
|
|
fi
|
|
done
|
|
|
|
# Copy ld-linux
|
|
if [[ -f /lib64/ld-linux-x86-64.so.2 ]]; then
|
|
mkdir -p "$rootfs/lib64"
|
|
cp /lib64/ld-linux-x86-64.so.2 "$rootfs/lib64/" 2>/dev/null || true
|
|
fi
|
|
}
|
|
|
|
install_busybox_tiny() {
|
|
local rootfs="$1"
|
|
|
|
info " Installing busybox-tiny userland..."
|
|
|
|
# Absolute minimal - just busybox
|
|
install_musl_minimal "$rootfs"
|
|
|
|
# Remove non-essential symlinks
|
|
rm -f "$rootfs/bin/awk" "$rootfs/bin/sed" "$rootfs/bin/grep"
|
|
}
|
|
|
|
create_essential_files() {
|
|
local rootfs="$1"
|
|
|
|
# /etc/passwd
|
|
cat > "$rootfs/etc/passwd" << 'EOF'
|
|
root:x:0:0:root:/root:/bin/sh
|
|
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
|
|
volt:x:1000:1000:Volt User:/home/volt:/bin/sh
|
|
EOF
|
|
|
|
# /etc/group
|
|
cat > "$rootfs/etc/group" << 'EOF'
|
|
root:x:0:
|
|
nobody:x:65534:
|
|
volt:x:1000:
|
|
EOF
|
|
|
|
# /etc/shadow (empty passwords - VMs use keys)
|
|
cat > "$rootfs/etc/shadow" << 'EOF'
|
|
root:*:19000:0:99999:7:::
|
|
nobody:*:19000:0:99999:7:::
|
|
volt:*:19000:0:99999:7:::
|
|
EOF
|
|
chmod 640 "$rootfs/etc/shadow"
|
|
|
|
# /etc/hosts
|
|
cat > "$rootfs/etc/hosts" << 'EOF'
|
|
127.0.0.1 localhost
|
|
::1 localhost ip6-localhost ip6-loopback
|
|
EOF
|
|
|
|
# /etc/hostname
|
|
echo "volt" > "$rootfs/etc/hostname"
|
|
|
|
# /etc/resolv.conf
|
|
cat > "$rootfs/etc/resolv.conf" << 'EOF'
|
|
nameserver 8.8.8.8
|
|
nameserver 8.8.4.4
|
|
EOF
|
|
|
|
# /etc/nsswitch.conf
|
|
cat > "$rootfs/etc/nsswitch.conf" << 'EOF'
|
|
passwd: files
|
|
group: files
|
|
shadow: files
|
|
hosts: files dns
|
|
networks: files
|
|
protocols: files
|
|
services: files
|
|
EOF
|
|
|
|
# /etc/os-release
|
|
cat > "$rootfs/etc/os-release" << 'EOF'
|
|
NAME="Volt Platform"
|
|
VERSION="1.0"
|
|
ID=volt
|
|
ID_LIKE=alpine
|
|
VERSION_ID=1.0
|
|
PRETTY_NAME="Volt Platform VM"
|
|
HOME_URL="https://voltvisor.io"
|
|
EOF
|
|
|
|
# Init script
|
|
cat > "$rootfs/sbin/init" << 'INIT'
|
|
#!/bin/sh
|
|
# Volt Init
|
|
|
|
# Mount essential filesystems
|
|
mount -t proc proc /proc
|
|
mount -t sysfs sysfs /sys
|
|
mount -t devtmpfs devtmpfs /dev
|
|
mkdir -p /dev/pts /dev/shm
|
|
mount -t devpts devpts /dev/pts
|
|
mount -t tmpfs tmpfs /dev/shm
|
|
mount -t tmpfs tmpfs /tmp
|
|
mount -t tmpfs tmpfs /run
|
|
|
|
# Set hostname
|
|
hostname -F /etc/hostname
|
|
|
|
# Network (if configured)
|
|
if [ -f /etc/network/interfaces ]; then
|
|
ifconfig lo up
|
|
fi
|
|
|
|
# Run init scripts
|
|
for script in /etc/init.d/S*; do
|
|
[ -x "$script" ] && "$script" start
|
|
done
|
|
|
|
# Start shell or configured service
|
|
if [ -f /etc/volt/service ]; then
|
|
exec $(cat /etc/volt/service)
|
|
else
|
|
exec /bin/sh
|
|
fi
|
|
INIT
|
|
chmod +x "$rootfs/sbin/init"
|
|
|
|
# Create init.d directory
|
|
mkdir -p "$rootfs/etc/init.d"
|
|
mkdir -p "$rootfs/etc/volt"
|
|
}
|
|
|
|
create_tinyvol() {
|
|
local image_name="$1"
|
|
local rootfs_dir="$2"
|
|
local output_path="$3"
|
|
|
|
log "Creating TinyVol: $output_path"
|
|
|
|
# Create squashfs image (TinyVol format)
|
|
# In production, this would use the actual TinyVol format
|
|
if command -v mksquashfs &>/dev/null; then
|
|
mksquashfs "$rootfs_dir" "$output_path" \
|
|
-comp zstd \
|
|
-Xcompression-level 19 \
|
|
-all-root \
|
|
-noappend \
|
|
-no-progress
|
|
else
|
|
# Fallback: create tar archive
|
|
warn "mksquashfs not found, creating tar archive"
|
|
tar -czf "$output_path" -C "$rootfs_dir" .
|
|
fi
|
|
|
|
local size=$(du -h "$output_path" | cut -f1)
|
|
info " Image size: $size"
|
|
}
|
|
|
|
generate_sbom() {
|
|
local image_name="$1"
|
|
local rootfs_dir="$2"
|
|
local output_path="$3"
|
|
|
|
log "Generating SBOM for: $image_name"
|
|
|
|
cat > "$output_path" << EOF
|
|
{
|
|
"bomFormat": "CycloneDX",
|
|
"specVersion": "1.5",
|
|
"version": 1,
|
|
"metadata": {
|
|
"timestamp": "$(date -Iseconds)",
|
|
"component": {
|
|
"type": "operating-system",
|
|
"name": "$image_name",
|
|
"version": "1.0"
|
|
}
|
|
},
|
|
"components": [
|
|
{
|
|
"type": "application",
|
|
"name": "busybox",
|
|
"version": "1.35.0"
|
|
}
|
|
]
|
|
}
|
|
EOF
|
|
}
|
|
|
|
sign_image() {
|
|
local image_name="$1"
|
|
local image_path="$2"
|
|
|
|
log "Signing image: $image_name"
|
|
|
|
# Generate checksums
|
|
sha256sum "$image_path" > "${image_path}.sha256"
|
|
|
|
# TODO: Integrate with ArmoredForge signing
|
|
# armored-forge sign "$image_path" --key volt-image-key
|
|
}
|
|
|
|
build_image() {
|
|
local image_name="$1"
|
|
local config_file="$CONFIG_DIR/$2"
|
|
|
|
log "=========================================="
|
|
log "Building image: $image_name"
|
|
log "=========================================="
|
|
|
|
if [[ ! -f "$config_file" ]]; then
|
|
warn "Config file not found: $config_file"
|
|
# Create default config
|
|
config_file="$CONFIG_DIR/server.yaml"
|
|
fi
|
|
|
|
local safe_name=$(echo "$image_name" | tr '/' '_')
|
|
local work_dir="$OUTPUT_DIR/.build/$safe_name"
|
|
local rootfs_dir="$work_dir/rootfs"
|
|
local image_path="$OUTPUT_DIR/$safe_name.tinyvol"
|
|
|
|
# Clean and create work directory
|
|
rm -rf "$work_dir"
|
|
mkdir -p "$work_dir" "$rootfs_dir"
|
|
|
|
# Build rootfs
|
|
build_rootfs "$image_name" "$config_file" "$rootfs_dir"
|
|
|
|
# Create TinyVol image
|
|
create_tinyvol "$image_name" "$rootfs_dir" "$image_path"
|
|
|
|
# Generate SBOM
|
|
generate_sbom "$image_name" "$rootfs_dir" "${image_path}.sbom.json"
|
|
|
|
# Sign image
|
|
sign_image "$image_name" "$image_path"
|
|
|
|
# Create image metadata
|
|
cat > "${image_path}.json" << EOF
|
|
{
|
|
"name": "$image_name",
|
|
"version": "1.0",
|
|
"created": "$(date -Iseconds)",
|
|
"size": "$(du -h "$image_path" | cut -f1)",
|
|
"sha256": "$(sha256sum "$image_path" | cut -d' ' -f1)",
|
|
"sbom": "${image_path}.sbom.json"
|
|
}
|
|
EOF
|
|
|
|
# Cleanup work directory
|
|
rm -rf "$work_dir"
|
|
|
|
log "Image built: $image_path"
|
|
}
|
|
|
|
main() {
|
|
log "Volt Platform Image Builder"
|
|
log "=============================="
|
|
|
|
mkdir -p "$OUTPUT_DIR" "$CACHE_DIR"
|
|
|
|
# Build all defined images
|
|
for image_name in "${!IMAGES[@]}"; do
|
|
build_image "$image_name" "${IMAGES[$image_name]}"
|
|
done
|
|
|
|
# If no images defined, build defaults
|
|
if [[ ${#IMAGES[@]} -eq 0 ]]; then
|
|
build_image "volt/server" "server.yaml"
|
|
build_image "volt/desktop-productivity" "desktop-productivity.yaml"
|
|
fi
|
|
|
|
log ""
|
|
log "Build complete!"
|
|
log "Images installed to: $OUTPUT_DIR"
|
|
ls -la "$OUTPUT_DIR"/*.tinyvol 2>/dev/null || ls -la "$OUTPUT_DIR"
|
|
}
|
|
|
|
# Run if executed directly
|
|
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
main "$@"
|
|
fi
|