particle-os-tools/install-apt-layer.sh
2025-07-15 11:18:41 -07:00

555 lines
17 KiB
Bash

# Embedded dependencies.json
APT_LAYER_DEPENDENCIES_JSON=$(cat << 'EOF'
{
"core": [
"chroot", "apt-get", "dpkg", "jq", "mount", "umount", "findmnt", "numfmt"
],
"container": [
"podman", "docker"
],
"oci": [
"skopeo"
],
"composefs": [
"mkcomposefs", "composefs-info", "mount.composefs", "mksquashfs", "unsquashfs"
],
"composefs_packages": [
"composefs", "libcomposefs1"
],
"bootloader": [
"efibootmgr", "grub-install", "update-grub", "bootctl"
],
"security": [
"curl", "wget", "gpg"
],
"package_install_commands": {
"debian": {
"composefs": "apt install -y composefs libcomposefs1",
"container": "apt install -y podman docker.io",
"oci": "apt install -y skopeo",
"bootloader": "apt install -y efibootmgr grub-common systemd-boot",
"core": "apt install -y squashfs-tools jq coreutils util-linux"
},
"fedora": {
"composefs": "dnf install -y composefs composefs-libs",
"container": "dnf install -y podman docker",
"oci": "dnf install -y skopeo",
"bootloader": "dnf install -y efibootmgr grub2-tools systemd-boot",
"core": "dnf install -y squashfs-tools jq coreutils util-linux"
}
}
}
EOF
)
#!/bin/bash
# apt-layer Installation Script
# This script installs the apt-layer tool, its dependencies, creates necessary directories and files,
# and sets up the system to use apt-layer. If already installed, it will update the tool.
#
# Usage:
# ./install-apt-layer.sh # Install or update apt-layer
# ./install-apt-layer.sh --uninstall # Remove apt-layer and all its files
# ./install-apt-layer.sh --reinstall # Remove and reinstall (reset to default state)
# ./install-apt-layer.sh --help # Show this help message
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
# Function to print colored output
print_status() {
echo -e "${GREEN}[INFO]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
print_header() {
echo -e "${BLUE}================================${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}================================${NC}"
}
# Function to show help
show_help() {
cat << 'EOF'
apt-layer Installation Script
This script installs the apt-layer tool, its dependencies, creates necessary directories and files,
and sets up the system to use apt-layer.
Usage:
./install-apt-layer.sh # Install or update apt-layer
./install-apt-layer.sh --uninstall # Remove apt-layer and all its files
./install-apt-layer.sh --reinstall # Remove and reinstall (reset to default state)
./install-apt-layer.sh --help # Show this help message
What this script does:
- Downloads the latest apt-layer.sh from the repository
- Installs required dependencies (jq, dos2unix, etc.)
- Creates necessary directories (/var/lib/apt-layer, /var/log/apt-layer, etc.)
- Sets up configuration files
- Makes apt-layer executable and available system-wide
- Initializes the apt-layer system
Dependencies:
- curl or wget (for downloading)
- jq (for JSON processing)
- dos2unix (for Windows line ending conversion)
- sudo (for system installation)
EOF
}
# Function to check if running as root
check_root() {
if [[ $EUID -eq 0 ]]; then
print_error "This script should not be run as root. Use sudo for specific commands."
exit 1
fi
}
# --- BEGIN DEPENDENCY JSON LOADING ---
# The dependencies JSON will be embedded as APT_LAYER_DEPENDENCIES_JSON in the compiled script.
APT_LAYER_DEPENDENCIES_JSON="${APT_LAYER_DEPENDENCIES_JSON:-}
{
\"core\": [\"chroot\", \"apt-get\", \"dpkg\", \"jq\", \"mount\", \"umount\", \"findmnt\", \"numfmt\"],
\"container\": [\"podman\", \"docker\"],
\"oci\": [\"skopeo\"],
\"composefs\": [\"mkcomposefs\", \"composefs-info\", \"mount.composefs\", \"mksquashfs\", \"unsquashfs\"],
\"bootloader\": [\"efibootmgr\", \"grub-install\", \"update-grub\", \"bootctl\"],
\"security\": [\"curl\", \"wget\", \"gpg\"]
}"
get_deps_for_type() {
local type="$1"
local json="$APT_LAYER_DEPENDENCIES_JSON"
case "$type" in
core)
echo "$json" | jq -r '.core[]'
;;
security)
echo "$json" | jq -r '.security[]'
;;
all)
echo "$json" | jq -r '.core[], .security[]'
;;
*)
echo "$json" | jq -r '.core[]'
;;
esac
}
print_install_instructions() {
local json="$APT_LAYER_DEPENDENCIES_JSON"
echo " Quick fix for common dependencies:"
echo " sudo apt install -y squashfs-tools jq coreutils util-linux skopeo"
echo ""
echo " For container support (choose one):"
echo " sudo apt install -y podman # or"
echo " sudo apt install -y docker.io"
echo ""
echo " For bootloader support:"
echo " sudo apt install -y efibootmgr grub-common systemd-boot"
echo ""
echo " For more information, run: apt-layer --help"
echo ""
}
# Function to check dependencies
check_dependencies() {
print_status "Checking dependencies..."
local missing_deps=()
# Installer-specific dependencies
local installer_deps=("sudo" "dos2unix")
# Use JSON to get core and security deps
local deps=( $(get_deps_for_type all) )
# Add installer deps
deps+=("sudo" "dos2unix")
# Check for curl or wget (at least one)
if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then
missing_deps+=("curl or wget")
fi
# Check all other deps
for dep in "${deps[@]}"; do
if [[ "$dep" == "curl" || "$dep" == "wget" ]]; then
continue # already checked above
fi
if ! command -v "$dep" >/dev/null 2>&1; then
missing_deps+=("$dep")
fi
done
if [[ ${#missing_deps[@]} -gt 0 ]]; then
print_error "Missing required dependencies: ${missing_deps[*]}"
print_install_instructions
print_error "Please install missing dependencies and re-run the installer."
exit 1
fi
print_status "All dependencies satisfied"
}
# Function to download apt-layer
download_apt_layer() {
print_status "Downloading apt-layer..."
local download_url="https://git.raines.xyz/robojerk/particle-os-tools/raw/branch/main/apt-layer.sh"
local temp_file="/tmp/apt-layer.sh"
# Download using curl or wget
if command -v curl >/dev/null 2>&1; then
if curl -L -o "$temp_file" "$download_url"; then
print_success "Downloaded apt-layer using curl"
else
print_error "Failed to download apt-layer using curl"
return 1
fi
elif command -v wget >/dev/null 2>&1; then
if wget -O "$temp_file" "$download_url"; then
print_success "Downloaded apt-layer using wget"
else
print_error "Failed to download apt-layer using wget"
return 1
fi
else
print_error "No download tool available (curl or wget)"
return 1
fi
# Verify the downloaded file
if [[ ! -f "$temp_file" ]] || [[ ! -s "$temp_file" ]]; then
print_error "Downloaded file is empty or missing"
return 1
fi
# Convert line endings if needed
if command -v dos2unix >/dev/null 2>&1; then
dos2unix "$temp_file" 2>/dev/null || true
fi
# Make executable
chmod +x "$temp_file"
print_success "apt-layer downloaded and prepared"
}
# Function to install apt-layer
install_apt_layer() {
print_status "Installing apt-layer..."
local temp_file="/tmp/apt-layer.sh"
local install_dir="/usr/local/bin"
local config_dir="/usr/local/etc/apt-layer"
# Create installation directory if it doesn't exist
sudo mkdir -p "$install_dir"
# Install apt-layer
sudo cp "$temp_file" "$install_dir/apt-layer"
sudo chmod +x "$install_dir/apt-layer"
# Create configuration directory
sudo mkdir -p "$config_dir"
# Create paths.json configuration
sudo tee "$config_dir/paths.json" >/dev/null <<EOF
{
"apt_layer_paths": {
"description": "apt-layer system path configuration",
"version": "1.0",
"main_directories": {
"workspace": {
"path": "/var/lib/apt-layer",
"description": "Main apt-layer workspace directory",
"permissions": "755",
"owner": "root:root"
},
"logs": {
"path": "/var/log/apt-layer",
"description": "apt-layer log files directory",
"permissions": "755",
"owner": "root:root"
},
"cache": {
"path": "/var/cache/apt-layer",
"description": "apt-layer cache directory",
"permissions": "755",
"owner": "root:root"
}
},
"workspace_subdirectories": {
"build": {
"path": "/var/lib/apt-layer/build",
"description": "Build artifacts and temporary files",
"permissions": "755",
"owner": "root:root"
},
"live_overlay": {
"path": "/var/lib/apt-layer/live-overlay",
"description": "Live overlay system for temporary changes",
"permissions": "755",
"owner": "root:root"
},
"composefs": {
"path": "/var/lib/apt-layer/composefs",
"description": "ComposeFS layers and metadata",
"permissions": "755",
"owner": "root:root"
},
"ostree_commits": {
"path": "/var/lib/apt-layer/ostree-commits",
"description": "OSTree commit history and metadata",
"permissions": "755",
"owner": "root:root"
},
"deployments": {
"path": "/var/lib/apt-layer/deployments",
"description": "Deployment directories and state",
"permissions": "755",
"owner": "root:root"
},
"history": {
"path": "/var/lib/apt-layer/history",
"description": "Deployment history and rollback data",
"permissions": "755",
"owner": "root:root"
},
"bootloader": {
"path": "/var/lib/apt-layer/bootloader",
"description": "Bootloader state and configuration",
"permissions": "755",
"owner": "root:root"
},
"transaction_state": {
"path": "/var/lib/apt-layer/transaction-state",
"description": "Transaction state and temporary data",
"permissions": "755",
"owner": "root:root"
}
},
"files": {
"deployment_db": {
"path": "/var/lib/apt-layer/deployments.json",
"description": "Deployment database file",
"permissions": "644",
"owner": "root:root"
},
"current_deployment": {
"path": "/var/lib/apt-layer/current-deployment",
"description": "Current deployment identifier file",
"permissions": "644",
"owner": "root:root"
},
"pending_deployment": {
"path": "/var/lib/apt-layer/pending-deployment",
"description": "Pending deployment identifier file",
"permissions": "644",
"owner": "root:root"
},
"transaction_log": {
"path": "/var/lib/apt-layer/transaction.log",
"description": "Transaction log file",
"permissions": "644",
"owner": "root:root"
}
},
"fallback_paths": {
"description": "Fallback paths for different environments",
"wsl": {
"workspace": "/mnt/wsl/apt-layer",
"logs": "/mnt/wsl/apt-layer/logs",
"cache": "/mnt/wsl/apt-layer/cache"
},
"container": {
"workspace": "/tmp/apt-layer",
"logs": "/tmp/apt-layer/logs",
"cache": "/tmp/apt-layer/cache"
},
"test": {
"workspace": "/tmp/apt-layer-test",
"logs": "/tmp/apt-layer-test/logs",
"cache": "/tmp/apt-layer-test/cache"
}
},
"environment_variables": {
"description": "Environment variable mappings",
"APT_LAYER_WORKSPACE": "workspace",
"APT_LAYER_LOG_DIR": "logs",
"APT_LAYER_CACHE_DIR": "cache",
"BUILD_DIR": "workspace_subdirectories.build",
"LIVE_OVERLAY_DIR": "workspace_subdirectories.live_overlay",
"COMPOSEFS_DIR": "workspace_subdirectories.composefs",
"OSTREE_COMMITS_DIR": "workspace_subdirectories.ostree_commits",
"DEPLOYMENTS_DIR": "workspace_subdirectories.deployments",
"HISTORY_DIR": "workspace_subdirectories.history",
"BOOTLOADER_STATE_DIR": "workspace_subdirectories.bootloader",
"TRANSACTION_STATE": "workspace_subdirectories.transaction_state",
"DEPLOYMENT_DB": "files.deployment_db",
"CURRENT_DEPLOYMENT_FILE": "files.current_deployment",
"PENDING_DEPLOYMENT_FILE": "files.pending_deployment",
"TRANSACTION_LOG": "files.transaction_log"
}
}
}
EOF
# Set proper permissions
sudo chmod 644 "$config_dir/paths.json"
# Initialize apt-layer system
print_status "Initializing apt-layer system..."
if sudo apt-layer --init; then
print_success "apt-layer system initialized"
else
print_warning "apt-layer system initialization failed, but installation completed"
fi
# Clean up temporary file
rm -f "$temp_file"
print_success "apt-layer installed successfully"
print_status "You can now use 'apt-layer --help' to see available commands"
}
# Function to uninstall apt-layer
uninstall_apt_layer() {
print_status "Uninstalling apt-layer..."
local install_dir="/usr/local/bin"
local config_dir="/usr/local/etc/apt-layer"
# Remove apt-layer binary
if [[ -f "$install_dir/apt-layer" ]]; then
sudo rm -f "$install_dir/apt-layer"
print_status "Removed apt-layer binary"
fi
# Remove configuration directory
if [[ -d "$config_dir" ]]; then
sudo rm -rf "$config_dir"
print_status "Removed configuration directory"
fi
# Remove apt-layer directories (optional - ask user)
if [[ -d "/var/lib/apt-layer" ]] || [[ -d "/var/log/apt-layer" ]] || [[ -d "/var/cache/apt-layer" ]]; then
echo -e "${YELLOW}Do you want to remove all apt-layer data directories? (y/N)${NC}"
read -r response
if [[ "$response" =~ ^[Yy]$ ]]; then
sudo rm -rf "/var/lib/apt-layer" "/var/log/apt-layer" "/var/cache/apt-layer"
print_status "Removed all apt-layer data directories"
else
print_status "Keeping apt-layer data directories"
fi
fi
print_success "apt-layer uninstalled successfully"
}
# Function to reinstall apt-layer
reinstall_apt_layer() {
print_status "Reinstalling apt-layer..."
# Uninstall first
uninstall_apt_layer
# Install again
install_apt_layer
print_success "apt-layer reinstalled successfully"
}
# Function to check if apt-layer is installed
is_apt_layer_installed() {
command -v apt-layer >/dev/null 2>&1
}
# Function to check if apt-layer is up to date
check_for_updates() {
print_status "Checking for updates..."
if ! is_apt_layer_installed; then
return 0 # Not installed, so no update needed
fi
# For now, we'll always download the latest version
# In the future, this could check version numbers or timestamps
return 1 # Update needed
}
# Main installation function
main_install() {
print_header "apt-layer Installation"
# Check if running as root
check_root
# Check dependencies
check_dependencies
# Check if already installed
if is_apt_layer_installed; then
print_status "apt-layer is already installed"
if check_for_updates; then
print_status "apt-layer is up to date"
return 0
else
print_status "Updating apt-layer..."
fi
else
print_status "Installing apt-layer..."
fi
# Download and install
if download_apt_layer && install_apt_layer; then
print_success "apt-layer installation completed successfully"
print_status "You can now use 'apt-layer --help' to see available commands"
return 0
else
print_error "apt-layer installation failed"
return 1
fi
}
# Main function
main() {
case "${1:-}" in
--help|-h)
show_help
exit 0
;;
--uninstall)
print_header "apt-layer Uninstallation"
uninstall_apt_layer
exit 0
;;
--reinstall)
print_header "apt-layer Reinstallation"
reinstall_apt_layer
exit 0
;;
"")
main_install
exit $?
;;
*)
print_error "Unknown option: $1"
show_help
exit 1
;;
esac
}
# Run main function with all arguments
main "$@"