create installation bundle

This commit is contained in:
2026-01-16 11:00:09 +00:00
parent 6f595775c4
commit 94e565257d
4 changed files with 1046 additions and 0 deletions

611
installer/airgap/create-bundle.sh Executable file
View File

@@ -0,0 +1,611 @@
#!/bin/bash
#
# Calypso Appliance - Airgap Bundle Creator
# Creates a self-contained installer bundle for offline deployment
# Includes all DEB packages with dependencies
#
# Usage: ./create-bundle.sh [--version VERSION] [--output OUTPUT_DIR] [--skip-packages]
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
INSTALLER_DIR="$PROJECT_ROOT/installer/alpha"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "\n${BLUE}==>${NC} $1"; }
# Configuration
VERSION="${CALYPSO_VERSION:-1.0.0}"
OUTPUT_DIR="${OUTPUT_DIR:-$PROJECT_ROOT/dist/airgap}"
BUNDLE_NAME="calypso-appliance-${VERSION}-airgap"
BUNDLE_DIR="$OUTPUT_DIR/$BUNDLE_NAME"
DOWNLOAD_PACKAGES="${DOWNLOAD_PACKAGES:-true}"
PACKAGE_LIST="$SCRIPT_DIR/package-list.txt"
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--version)
VERSION="$2"
shift 2
;;
--output)
OUTPUT_DIR="$2"
shift 2
;;
--skip-packages)
DOWNLOAD_PACKAGES=false
shift
;;
*)
log_error "Unknown option: $1"
exit 1
;;
esac
done
# Function to get all dependencies recursively
get_all_dependencies() {
local package=$1
local deps_file=$2
local visited=$3
# Check if already visited
if grep -q "^${package}$" "$visited" 2>/dev/null; then
return 0
fi
echo "$package" >> "$visited"
# Get dependencies (Depends, PreDepends, Recommends)
local deps=$(apt-cache depends "$package" 2>/dev/null | \
grep -E "^\s*(Depends|PreDepends|Recommends):" | \
awk '{print $2}' | \
grep -v "^<" | \
sort -u || true)
for dep in $deps; do
# Skip virtual packages and already processed
if apt-cache show "$dep" &>/dev/null 2>&1; then
if ! grep -q "^${dep}$" "$deps_file" 2>/dev/null; then
echo "$dep" >> "$deps_file"
get_all_dependencies "$dep" "$deps_file" "$visited"
fi
fi
done
}
# Function to download packages with all dependencies
download_packages() {
log_step "Downloading packages with dependencies..."
local DEBS_DIR="$BUNDLE_DIR/packages/debs"
mkdir -p "$DEBS_DIR"
# Create temporary files
local ALL_PACKAGES=$(mktemp)
local VISITED=$(mktemp)
local FINAL_LIST=$(mktemp)
# Read package list and get all dependencies
log_info "Resolving dependencies for all packages..."
local count=0
local total=$(grep -v '^#' "$PACKAGE_LIST" | grep -v '^$' | wc -l)
while IFS= read -r package || [ -n "$package" ]; do
# Skip comments and empty lines
[[ "$package" =~ ^#.*$ ]] && continue
[[ -z "$package" ]] && continue
count=$((count + 1))
log_info "[$count/$total] Processing: $package"
get_all_dependencies "$package" "$ALL_PACKAGES" "$VISITED"
echo "$package" >> "$ALL_PACKAGES"
done < "$PACKAGE_LIST"
# Remove duplicates and sort
sort -u "$ALL_PACKAGES" > "$FINAL_LIST"
local total_with_deps=$(wc -l < "$FINAL_LIST")
log_info "Total packages (with dependencies): $total_with_deps"
# Download all packages
log_info "Downloading packages..."
cd "$DEBS_DIR"
local downloaded=0
local failed=0
while IFS= read -r package || [ -n "$package" ]; do
# Try to download package
if apt-get download "$package" 2>/dev/null; then
downloaded=$((downloaded + 1))
echo "$package" >> "$BUNDLE_DIR/packages/downloaded-packages.txt"
else
failed=$((failed + 1))
echo "$package (not available)" >> "$BUNDLE_DIR/packages/missing-packages.txt"
log_warn "Package not available: $package"
fi
done < "$FINAL_LIST"
# Cleanup temp files
rm -f "$ALL_PACKAGES" "$VISITED" "$FINAL_LIST"
log_info "✓ Downloaded $downloaded packages"
if [ -f "$BUNDLE_DIR/packages/missing-packages.txt" ] && [ "$failed" -gt 0 ]; then
log_warn "$failed packages could not be downloaded"
log_warn "Check: $BUNDLE_DIR/packages/missing-packages.txt"
fi
# Create Packages index for local repository
if [ "$downloaded" -gt 0 ]; then
log_info "Creating Packages index..."
cd "$DEBS_DIR"
dpkg-scanpackages . /dev/null 2>/dev/null | gzip -9c > Packages.gz || true
log_info "✓ Packages index created"
fi
}
log_step "Creating Airgap Bundle for Calypso Appliance v${VERSION}"
# Create bundle directory structure
log_info "Creating bundle directory structure..."
rm -rf "$BUNDLE_DIR"
mkdir -p "$BUNDLE_DIR"/{binaries,packages/debs,configs,scripts,services,migrations,frontend,third_party,docs}
# Copy package list
cp "$PACKAGE_LIST" "$BUNDLE_DIR/packages/package-list.txt"
# 1. Download packages if requested
if [[ "$DOWNLOAD_PACKAGES" == "true" ]]; then
# Check if running on Ubuntu/Debian
if [ ! -f /etc/debian_version ]; then
log_warn "Not running on Debian/Ubuntu. Skipping package download."
log_warn "Run download-packages.sh later on Ubuntu 24.04 machine"
DOWNLOAD_PACKAGES=false
else
# Update package lists
log_info "Updating package lists..."
apt-get update -qq
download_packages
fi
else
log_info "Skipping package download (use --skip-packages to suppress this message)"
fi
# 2. Build and bundle Calypso binaries
log_step "Building Calypso binaries..."
# Build backend
log_info "Building backend..."
cd "$PROJECT_ROOT/backend"
if [ ! -f "go.mod" ]; then
log_error "Backend directory not found or invalid"
exit 1
fi
# Ensure Go is available
export PATH=$PATH:/usr/local/go/bin
if ! command -v go &> /dev/null; then
log_error "Go is not installed or not in PATH"
log_error "Please install Go first or use --skip-packages and build manually"
exit 1
fi
# Build for Linux amd64
export CGO_ENABLED=0
export GOOS=linux
export GOARCH=amd64
go build -ldflags="-s -w -X main.version=${VERSION} -X main.buildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ) -X main.gitCommit=$(git rev-parse --short HEAD 2>/dev/null || echo 'unknown')" \
-o "$BUNDLE_DIR/binaries/calypso-api" ./cmd/calypso-api
log_info "✓ Backend binary built"
# Build frontend
log_info "Building frontend..."
cd "$PROJECT_ROOT/frontend"
if [ ! -f "package.json" ]; then
log_error "Frontend directory not found or invalid"
exit 1
fi
# Ensure Node.js is available
if ! command -v npm &> /dev/null; then
log_error "Node.js/npm is not installed or not in PATH"
log_error "Please install Node.js first or use --skip-packages and build manually"
exit 1
fi
npm ci
npm run build
# Copy frontend dist
cp -r dist/* "$BUNDLE_DIR/frontend/"
log_info "✓ Frontend built"
# 3. Bundle database migrations
log_step "Bundling database migrations..."
if [ -d "$PROJECT_ROOT/backend/internal/common/database/migrations" ]; then
cp -r "$PROJECT_ROOT/backend/internal/common/database/migrations"/* "$BUNDLE_DIR/migrations/"
log_info "✓ Migrations bundled"
else
log_warn "Migrations directory not found"
fi
# 4. Bundle installer scripts
log_step "Bundling installer scripts..."
cp -r "$INSTALLER_DIR/scripts"/* "$BUNDLE_DIR/scripts/" 2>/dev/null || true
cp "$INSTALLER_DIR/install.sh" "$BUNDLE_DIR/" 2>/dev/null || true
cp "$INSTALLER_DIR/uninstall.sh" "$BUNDLE_DIR/" 2>/dev/null || true
log_info "✓ Installer scripts bundled"
# 5. Bundle systemd services
log_step "Bundling systemd services..."
if [ -d "$PROJECT_ROOT/deploy/systemd" ]; then
cp -r "$PROJECT_ROOT/deploy/systemd"/* "$BUNDLE_DIR/services/"
log_info "✓ Systemd services bundled"
fi
# 6. Bundle configuration templates
log_step "Bundling configuration templates..."
if [ -d "$INSTALLER_DIR/configs" ]; then
cp -r "$INSTALLER_DIR/configs"/* "$BUNDLE_DIR/configs/"
log_info "✓ Configuration templates bundled"
fi
# 7. Create offline installer script
log_step "Creating offline installer script..."
cat > "$BUNDLE_DIR/install-airgap.sh" <<'INSTALLER_EOF'
#!/bin/bash
#
# Calypso Appliance - Airgap Installer
# Installs Calypso from bundled files without internet connection
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BUNDLE_DIR="$SCRIPT_DIR"
VERSION="${VERSION:-1.0.0}"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "\n${BLUE}==>${NC} $1"; }
# Check root
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root (use sudo)"
exit 1
fi
# Check OS
if [ ! -f /etc/os-release ]; then
log_error "Cannot detect OS"
exit 1
fi
. /etc/os-release
if [[ "$ID" != "ubuntu" ]] || [[ "$VERSION_ID" != "24.04" ]]; then
log_warn "This installer is designed for Ubuntu 24.04 LTS"
log_warn "Detected: $ID $VERSION_ID"
read -p "Continue anyway? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
log_step "Calypso Appliance - Airgap Installation"
log_info "Bundle directory: $BUNDLE_DIR"
# 1. Install packages from bundle
log_step "Installing system packages from bundle..."
if [ -d "$BUNDLE_DIR/packages/debs" ] && [ "$(ls -A $BUNDLE_DIR/packages/debs/*.deb 2>/dev/null)" ]; then
log_info "Installing packages from bundle..."
# Create local apt repository
DEBS_DIR="$BUNDLE_DIR/packages/debs"
REPO_DIR="/tmp/calypso-local-repo"
mkdir -p "$REPO_DIR"
cp "$DEBS_DIR"/*.deb "$REPO_DIR/" 2>/dev/null || true
# Create Packages.gz if not exists
cd "$REPO_DIR"
if [ ! -f Packages.gz ]; then
dpkg-scanpackages . /dev/null 2>/dev/null | gzip -9c > Packages.gz
fi
# Add to sources.list temporarily
echo "deb [trusted=yes] file://$REPO_DIR ./" > /tmp/calypso-local.list
mv /tmp/calypso-local.list /etc/apt/sources.list.d/calypso-local.list
# Update apt cache
apt-get update -qq
# Install packages from list
PACKAGE_LIST="$BUNDLE_DIR/packages/package-list.txt"
PACKAGES=$(grep -v '^#' "$PACKAGE_LIST" | grep -v '^$' | tr '\n' ' ')
log_info "Installing packages..."
apt-get install -y --allow-unauthenticated $PACKAGES || {
log_warn "Some packages failed to install, trying individual installation..."
while IFS= read -r package || [ -n "$package" ]; do
[[ "$package" =~ ^#.*$ ]] && continue
[[ -z "$package" ]] && continue
apt-get install -y --allow-unauthenticated "$package" 2>/dev/null || log_warn "Failed: $package"
done < "$PACKAGE_LIST"
}
# Cleanup
rm -f /etc/apt/sources.list.d/calypso-local.list
apt-get update -qq
log_info "✓ Packages installed from bundle"
else
log_warn "Package DEB files not found in bundle"
log_warn "You need to install packages manually"
log_info "Package list: $BUNDLE_DIR/packages/package-list.txt"
read -p "Install packages from system repositories? (y/N) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
apt-get update
xargs -a "$BUNDLE_DIR/packages/package-list.txt" -r apt-get install -y
fi
fi
# 2. Install Go from bundle (if available)
if [ -f "$BUNDLE_DIR/third_party/go.tar.gz" ]; then
log_step "Installing Go from bundle..."
rm -rf /usr/local/go
tar -C /usr/local -xzf "$BUNDLE_DIR/third_party/go.tar.gz"
export PATH=$PATH:/usr/local/go/bin
if ! grep -q "/usr/local/go/bin" /etc/profile; then
echo 'export PATH=$PATH:/usr/local/go/bin' >> /etc/profile
fi
log_info "✓ Go installed"
fi
# 3. Install Node.js from bundle (if available)
if [ -f "$BUNDLE_DIR/third_party/nodejs.tar.xz" ]; then
log_step "Installing Node.js from bundle..."
rm -rf /usr/local/node
mkdir -p /usr/local/node
tar -C /usr/local/node -xJf "$BUNDLE_DIR/third_party/nodejs.tar.xz" --strip-components=1
export PATH=$PATH:/usr/local/node/bin
if ! grep -q "/usr/local/node/bin" /etc/profile; then
echo 'export PATH=$PATH:/usr/local/node/bin' >> /etc/profile
fi
log_info "✓ Node.js installed"
fi
# 4. Create filesystem structure
log_step "Creating filesystem structure..."
INSTALL_PREFIX="/opt/adastra/calypso"
CONFIG_DIR="/etc/calypso"
DATA_DIR="/srv/calypso"
LOG_DIR="/var/log/calypso"
LIB_DIR="/var/lib/calypso"
mkdir -p "$INSTALL_PREFIX/releases/${VERSION}"/{bin,web,migrations,scripts}
mkdir -p "$INSTALL_PREFIX/third_party"
mkdir -p "$CONFIG_DIR"/{tls,integrations,system,scst,nfs,samba,clamav}
mkdir -p "$DATA_DIR"/{db,backups,object,shares,vtl,iscsi,uploads,cache,_system,quarantine}
mkdir -p "$LOG_DIR" "$LIB_DIR" "/run/calypso"
# Create calypso user
if ! id "calypso" &>/dev/null; then
useradd -r -s /bin/false -d "$LIB_DIR" -c "Calypso Appliance" calypso
fi
# 5. Install binaries
log_step "Installing Calypso binaries..."
cp "$BUNDLE_DIR/binaries/calypso-api" "$INSTALL_PREFIX/releases/${VERSION}/bin/"
chmod +x "$INSTALL_PREFIX/releases/${VERSION}/bin/calypso-api"
# Install frontend
cp -r "$BUNDLE_DIR/frontend"/* "$INSTALL_PREFIX/releases/${VERSION}/web/"
# Install migrations
cp -r "$BUNDLE_DIR/migrations"/* "$INSTALL_PREFIX/releases/${VERSION}/migrations/" 2>/dev/null || true
# Install scripts
cp -r "$BUNDLE_DIR/scripts"/* "$INSTALL_PREFIX/releases/${VERSION}/scripts/" 2>/dev/null || true
chmod +x "$INSTALL_PREFIX/releases/${VERSION}/scripts"/*.sh 2>/dev/null || true
# Create symlink
ln -sfn "releases/${VERSION}" "$INSTALL_PREFIX/current"
# Set ownership
chown -R calypso:calypso "$INSTALL_PREFIX" "$LIB_DIR" "$LOG_DIR" "$DATA_DIR" 2>/dev/null || chown -R root:root "$INSTALL_PREFIX" "$LIB_DIR" "$LOG_DIR" "$DATA_DIR"
log_info "✓ Binaries installed"
# 6. Install systemd services
log_step "Installing systemd services..."
if [ -d "$BUNDLE_DIR/services" ]; then
cp "$BUNDLE_DIR/services"/*.service /etc/systemd/system/ 2>/dev/null || true
systemctl daemon-reload
log_info "✓ Systemd services installed"
fi
# 7. Setup database
log_step "Setting up database..."
if systemctl is-active --quiet postgresql 2>/dev/null || systemctl is-enabled --quiet postgresql 2>/dev/null; then
sudo -u postgres createdb calypso 2>/dev/null || true
sudo -u postgres createuser calypso 2>/dev/null || true
sudo -u postgres psql -c "ALTER USER calypso WITH PASSWORD 'calypso_change_me';" 2>/dev/null || true
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE calypso TO calypso;" 2>/dev/null || true
log_info "✓ Database setup complete"
else
log_warn "PostgreSQL not running. Please start it and run database setup manually."
fi
# 8. Generate configuration
log_step "Generating configuration..."
if [ -f "$BUNDLE_DIR/configs/config.yaml.template" ]; then
cp "$BUNDLE_DIR/configs/config.yaml.template" "$CONFIG_DIR/calypso.yaml"
# Generate secrets
if command -v openssl &> /dev/null; then
JWT_SECRET=$(openssl rand -hex 32)
sed -i "s/CHANGE_ME_JWT_SECRET/$JWT_SECRET/g" "$CONFIG_DIR/calypso.yaml"
fi
log_info "✓ Configuration generated"
fi
log_step "Installation Complete!"
log_info ""
log_info "Next steps:"
log_info "1. Review configuration: $CONFIG_DIR/calypso.yaml"
log_info "2. Install kernel modules (ZFS, SCST, mhVTL) if needed"
log_info "3. Start services: systemctl start calypso-api"
log_info "4. Enable services: systemctl enable calypso-api"
log_info ""
log_info "Calypso API will be available at: http://localhost:8080"
INSTALLER_EOF
chmod +x "$BUNDLE_DIR/install-airgap.sh"
# 8. Create README
cat > "$BUNDLE_DIR/README.md" <<EOF
# Calypso Appliance - Airgap Installation Bundle
**Version:** ${VERSION}
**Target OS:** Ubuntu Server 24.04 LTS
**Installation Type:** Offline/Airgap
## Bundle Contents
- **binaries/**: Pre-built Calypso binaries
- **frontend/**: Built frontend assets
- **packages/debs/**: All required DEB packages with dependencies (59 packages + ~200-300 dependencies)
- **scripts/**: Installation scripts
- **services/**: Systemd service files
- **migrations/**: Database migration files
- **configs/**: Configuration templates
- **third_party/**: Optional third-party binaries (Go, Node.js)
## Installation
### Prerequisites
- Ubuntu Server 24.04 LTS
- Root or sudo access
- Minimum 10GB free disk space
- Kernel headers installed (for kernel modules)
### Quick Install
\`\`\`bash
cd /path/to/bundle
sudo ./install-airgap.sh
\`\`\`
The installer will:
1. Install all packages from bundled DEB files
2. Install Calypso binaries and frontend
3. Setup database and configuration
4. Install systemd services
### Post-Installation
1. Review configuration: \`/etc/calypso/calypso.yaml\`
2. Install kernel modules (ZFS, SCST, mhVTL) if needed
3. Start services: \`systemctl start calypso-api\`
4. Enable services: \`systemctl enable calypso-api\`
## Verification
Check service status:
\`\`\`bash
systemctl status calypso-api
curl http://localhost:8080/api/v1/health
\`\`\`
## Support
For issues, check:
- Installation logs: \`/var/log/calypso/install.log\`
- Service logs: \`journalctl -u calypso-api\`
EOF
# 9. Create third-party binaries download script
cat > "$BUNDLE_DIR/third_party/download-binaries.sh" <<'BIN_EOF'
#!/bin/bash
#
# Download third-party binaries for airgap installation
# Run this on an internet-connected machine
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
log_info() { echo -e "\033[0;32m[INFO]\033[0m $1"; }
# Download Go
log_info "Downloading Go 1.22..."
GO_VERSION="1.22.0"
GO_ARCH="linux-amd64"
wget -q "https://go.dev/dl/go${GO_VERSION}.${GO_ARCH}.tar.gz" -O "$SCRIPT_DIR/go.tar.gz"
log_info "✓ Go downloaded"
# Download Node.js
log_info "Downloading Node.js 20.x LTS..."
NODE_VERSION="20.18.0"
NODE_ARCH="linux-x64"
wget -q "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-${NODE_ARCH}.tar.xz" -O "$SCRIPT_DIR/nodejs.tar.xz"
log_info "✓ Node.js downloaded"
log_info "✓ All binaries downloaded"
BIN_EOF
chmod +x "$BUNDLE_DIR/third_party/download-binaries.sh"
# 10. Create archive
log_step "Creating archive..."
cd "$OUTPUT_DIR"
tar -czf "${BUNDLE_NAME}.tar.gz" "$BUNDLE_NAME"
# Calculate checksum
sha256sum "${BUNDLE_NAME}.tar.gz" > "${BUNDLE_NAME}.tar.gz.sha256"
log_step "Bundle Creation Complete!"
log_info ""
log_info "Bundle location: $OUTPUT_DIR/${BUNDLE_NAME}.tar.gz"
log_info "Bundle size: $(du -h "$OUTPUT_DIR/${BUNDLE_NAME}.tar.gz" | cut -f1)"
log_info "Checksum: $OUTPUT_DIR/${BUNDLE_NAME}.tar.gz.sha256"
if [ -d "$BUNDLE_DIR/packages/debs" ]; then
pkg_count=$(ls -1 "$BUNDLE_DIR/packages/debs"/*.deb 2>/dev/null | wc -l)
log_info "Packages bundled: $pkg_count DEB files"
fi
log_info ""
log_info "To deploy:"
log_info "1. Copy ${BUNDLE_NAME}.tar.gz to target machine"
log_info "2. Extract: tar -xzf ${BUNDLE_NAME}.tar.gz"
log_info "3. Run: sudo ./${BUNDLE_NAME}/install-airgap.sh"