#!/bin/bash # Particle-OS OCI Integration # Provides OCI export/import functionality for ComposeFS images set -euo pipefail # Source unified configuration if [[ -f "${PARTICLE_CONFIG_FILE:-/usr/local/etc/particle-config.sh}" ]]; then source "${PARTICLE_CONFIG_FILE:-/usr/local/etc/particle-config.sh}" else # Fallback configuration PARTICLE_WORKSPACE="${PARTICLE_WORKSPACE:-/var/lib/particle-os}" PARTICLE_CONFIG_DIR="${PARTICLE_CONFIG_DIR:-/usr/local/etc/particle-os}" PARTICLE_LOG_DIR="${PARTICLE_LOG_DIR:-/var/log/particle-os}" PARTICLE_CACHE_DIR="${PARTICLE_CACHE_DIR:-/var/cache/particle-os}" COMPOSEFS_SCRIPT="${PARTICLE_COMPOSEFS_SCRIPT:-/usr/local/bin/composefs-alternative.sh}" PARTICLE_CONTAINER_RUNTIME="${PARTICLE_CONTAINER_RUNTIME:-podman}" PARTICLE_TEMP_DIR="${PARTICLE_TEMP_DIR:-/tmp/particle-oci-$$}" fi # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # Logging functions log_info() { echo -e "${BLUE}[INFO]${NC} $1" } log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } # Cleanup function cleanup() { if [[ -d "$PARTICLE_TEMP_DIR" ]]; then log_info "Cleaning up temporary directory: $PARTICLE_TEMP_DIR" rm -rf "$PARTICLE_TEMP_DIR" 2>/dev/null || true fi } # Set up trap for cleanup trap cleanup EXIT # OCI export functions export_composefs_to_oci() { local composefs_image="$1" local oci_image_name="$2" local oci_tag="${3:-latest}" local full_oci_name="$oci_image_name:$oci_tag" log_info "Exporting ComposeFS image to OCI: $composefs_image -> $full_oci_name" # Create temporary directory mkdir -p "$PARTICLE_TEMP_DIR" # Mount ComposeFS image local mount_point="$PARTICLE_TEMP_DIR/mount" mkdir -p "$mount_point" log_info "Mounting ComposeFS image..." if ! "$COMPOSEFS_SCRIPT" mount "$composefs_image" "$mount_point"; then log_error "Failed to mount ComposeFS image: $composefs_image" return 1 fi # Create Containerfile local containerfile="$PARTICLE_TEMP_DIR/Containerfile" cat > "$containerfile" << EOF FROM scratch COPY . / LABEL org.ubuntu.ublue.image="$composefs_image" LABEL org.ubuntu.ublue.type="composefs-export" LABEL org.ubuntu.ublue.created="$(date -Iseconds)" CMD ["/bin/bash"] EOF # Build OCI image log_info "Building OCI image..." if ! "$PARTICLE_CONTAINER_RUNTIME" build -f "$containerfile" -t "$full_oci_name" "$mount_point"; then log_error "Failed to build OCI image: $full_oci_name" "$COMPOSEFS_SCRIPT" unmount "$mount_point" return 1 fi # Unmount ComposeFS image "$COMPOSEFS_SCRIPT" unmount "$mount_point" log_success "ComposeFS image exported to OCI: $full_oci_name" return 0 } # OCI import functions import_oci_to_composefs() { local oci_image_name="$1" local composefs_image="$2" local oci_tag="${3:-latest}" local full_oci_name="$oci_image_name:$oci_tag" log_info "Importing OCI image to ComposeFS: $full_oci_name -> $composefs_image" # Create temporary directory mkdir -p "$PARTICLE_TEMP_DIR" # Create temporary container and export filesystem local container_name="particle-import-$$" log_info "Creating temporary container..." if ! "$PARTICLE_CONTAINER_RUNTIME" create --name "$container_name" "$full_oci_name"; then log_error "Failed to create temporary container from: $full_oci_name" return 1 fi # Export container filesystem local export_dir="$PARTICLE_TEMP_DIR/export" mkdir -p "$export_dir" log_info "Exporting container filesystem..." if ! "$PARTICLE_CONTAINER_RUNTIME" export "$container_name" | tar -xf - -C "$export_dir"; then log_error "Failed to export container filesystem" "$PARTICLE_CONTAINER_RUNTIME" rm "$container_name" >/dev/null 2>&1 || true return 1 fi # Remove temporary container "$PARTICLE_CONTAINER_RUNTIME" rm "$container_name" >/dev/null 2>&1 || true # Clean up device files and ephemeral directories that can't be in ComposeFS log_info "Cleaning up device files and ephemeral directories..." rm -rf "$export_dir"/{dev,proc,sys}/* 2>/dev/null || true # Remove temporary and ephemeral directories rm -rf "$export_dir"/tmp/* 2>/dev/null || true rm -rf "$export_dir"/var/tmp/* 2>/dev/null || true rm -rf "$export_dir"/run/* 2>/dev/null || true rm -rf "$export_dir"/mnt/* 2>/dev/null || true rm -rf "$export_dir"/media/* 2>/dev/null || true # Create ComposeFS image log_info "Creating ComposeFS image..." if ! "$COMPOSEFS_SCRIPT" create "$composefs_image" "$export_dir"; then log_error "Failed to create ComposeFS image: $composefs_image" return 1 fi log_success "OCI image imported to ComposeFS: $composefs_image" return 0 } # OCI push/pull functions push_oci_image() { local oci_image_name="$1" local registry_url="${2:-}" local oci_tag="${3:-latest}" log_info "Pushing OCI image: $oci_image_name:$oci_tag" # Add registry prefix if provided local full_image_name="$oci_image_name" if [[ -n "$registry_url" ]]; then full_image_name="$registry_url/$oci_image_name" fi # Tag image with full name if ! "$PARTICLE_CONTAINER_RUNTIME" tag "$oci_image_name:$oci_tag" "$full_image_name:$oci_tag"; then log_error "Failed to tag image: $full_image_name:$oci_tag" return 1 fi # Push to registry if ! "$PARTICLE_CONTAINER_RUNTIME" push "$full_image_name:$oci_tag"; then log_error "Failed to push image: $full_image_name:$oci_tag" return 1 fi log_success "OCI image pushed: $full_image_name:$oci_tag" return 0 } pull_oci_image() { local oci_image_name="$1" local registry_url="${2:-}" local oci_tag="${3:-latest}" log_info "Pulling OCI image: $oci_image_name:$oci_tag" # Add registry prefix if provided local full_image_name="$oci_image_name" if [[ -n "$registry_url" ]]; then full_image_name="$registry_url/$oci_image_name" fi # Pull from registry if ! "$PARTICLE_CONTAINER_RUNTIME" pull "$full_image_name:$oci_tag"; then log_error "Failed to pull image: $full_image_name:$oci_tag" return 1 fi # Tag with local name if ! "$PARTICLE_CONTAINER_RUNTIME" tag "$full_image_name:$oci_tag" "$oci_image_name:$oci_tag"; then log_error "Failed to tag image: $oci_image_name:$oci_tag" return 1 fi log_success "OCI image pulled: $oci_image_name:$oci_tag" return 0 } # OCI image inspection inspect_oci_image() { local oci_image_name="$1" local oci_tag="${2:-latest}" local full_image_name="$oci_image_name:$oci_tag" log_info "Inspecting OCI image: $full_image_name" if ! "$PARTICLE_CONTAINER_RUNTIME" inspect "$full_image_name"; then log_error "Failed to inspect image: $full_image_name" return 1 fi return 0 } # List OCI images list_oci_images() { log_info "Listing OCI images:" echo "$PARTICLE_CONTAINER_RUNTIME" images } # Remove OCI image remove_oci_image() { local oci_image_name="$1" local oci_tag="${2:-latest}" local full_image_name="$oci_image_name:$oci_tag" log_info "Removing OCI image: $full_image_name" if ! "$PARTICLE_CONTAINER_RUNTIME" rmi "$full_image_name"; then log_error "Failed to remove image: $full_image_name" return 1 fi log_success "OCI image removed: $full_image_name" return 0 } # Integration with apt-layer.sh integrate_with_apt_layer() { local operation="$1" local composefs_image="$2" local oci_image_name="$3" local oci_tag="${4:-latest}" case "$operation" in "export") log_info "Integrating OCI export with apt-layer: $composefs_image" export_composefs_to_oci "$composefs_image" "$oci_image_name" "$oci_tag" ;; "import") log_info "Integrating OCI import with apt-layer: $oci_image_name" import_oci_to_composefs "$oci_image_name" "$composefs_image" "$oci_tag" ;; *) log_error "Unknown operation: $operation" return 1 ;; esac return 0 } # Main function main() { # Check dependencies if [[ ! -f "$COMPOSEFS_SCRIPT" ]]; then log_error "composefs-alternative.sh not found at: $COMPOSEFS_SCRIPT" exit 1 fi if ! command -v "$PARTICLE_CONTAINER_RUNTIME" >/dev/null 2>&1; then log_error "Container runtime not found: $PARTICLE_CONTAINER_RUNTIME" exit 1 fi # Parse command line arguments case "${1:-}" in "export") if [[ -z "${2:-}" ]] || [[ -z "${3:-}" ]]; then log_error "ComposeFS image and OCI image name required for export" exit 1 fi export_composefs_to_oci "$2" "$3" "${4:-}" ;; "import") if [[ -z "${2:-}" ]] || [[ -z "${3:-}" ]]; then log_error "OCI image name and ComposeFS image required for import" exit 1 fi import_oci_to_composefs "$2" "$3" "${4:-}" ;; "push") if [[ -z "${2:-}" ]]; then log_error "OCI image name required for push" exit 1 fi push_oci_image "$2" "${3:-}" "${4:-}" ;; "pull") if [[ -z "${2:-}" ]]; then log_error "OCI image name required for pull" exit 1 fi pull_oci_image "$2" "${3:-}" "${4:-}" ;; "inspect") if [[ -z "${2:-}" ]]; then log_error "OCI image name required for inspect" exit 1 fi inspect_oci_image "$2" "${3:-}" ;; "list") list_oci_images ;; "remove") if [[ -z "${2:-}" ]]; then log_error "OCI image name required for remove" exit 1 fi remove_oci_image "$2" "${3:-}" ;; "integrate") if [[ -z "${2:-}" ]] || [[ -z "${3:-}" ]] || [[ -z "${4:-}" ]]; then log_error "Operation, ComposeFS image, and OCI image name required for integrate" exit 1 fi integrate_with_apt_layer "$2" "$3" "$4" "${5:-}" ;; "help"|"-h"|"--help") cat << EOF Particle-OS OCI Integration Usage: $0 [options] Commands: export [tag] Export ComposeFS image to OCI import [tag] Import OCI image to ComposeFS push [registry] [tag] Push OCI image to registry pull [registry] [tag] Pull OCI image from registry inspect [tag] Inspect OCI image list List OCI images remove [tag] Remove OCI image integrate [tag] Integrate with apt-layer.sh Operations for integrate: export Export ComposeFS to OCI import Import OCI to ComposeFS Examples: $0 export particle-os/gaming/24.04 particle-os/gaming:latest $0 import ubuntu:24.04 particle-os/base/24.04 $0 push particle-os/gaming registry.example.com latest $0 integrate export particle-os/gaming/24.04 particle-os/gaming Environment Variables: PARTICLE_CONTAINER_RUNTIME=podman Container runtime to use PARTICLE_REGISTRY_URL=registry.example.com Default registry URL PARTICLE_CONFIG_FILE=/path/to/config.sh Configuration file path PARTICLE_WORKSPACE=/var/lib/particle-os Workspace directory PARTICLE_COMPOSEFS_SCRIPT=/path/to/composefs ComposeFS script path EOF ;; *) log_error "Unknown command: ${1:-}" echo "Use '$0 help' for usage information" exit 1 ;; esac } # Run main function main "$@"