462 lines
14 KiB
Bash
462 lines
14 KiB
Bash
|
|
#!/bin/bash
|
||
|
|
# Custom ISO builder that works on ARM Mac by modifying pre-built Alpine ISO
|
||
|
|
# instead of building from scratch
|
||
|
|
|
||
|
|
set -e
|
||
|
|
|
||
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
|
|
ALPINE_VERSION="${ALPINE_VERSION:-3.19}"
|
||
|
|
ARCH="${ARCH:-x86_64}"
|
||
|
|
ISO_URL="https://dl-cdn.alpinelinux.org/alpine/v${ALPINE_VERSION}/releases/${ARCH}/alpine-standard-${ALPINE_VERSION}.0-${ARCH}.iso"
|
||
|
|
WORK_DIR="$SCRIPT_DIR/build/iso-custom"
|
||
|
|
OUTPUT_DIR="$SCRIPT_DIR/results"
|
||
|
|
|
||
|
|
echo "🏔️ Building custom Archipelago ISO from Alpine base"
|
||
|
|
echo ""
|
||
|
|
echo "📋 Configuration:"
|
||
|
|
echo " Alpine Version: $ALPINE_VERSION"
|
||
|
|
echo " Architecture: $ARCH"
|
||
|
|
echo " Base ISO URL: $ISO_URL"
|
||
|
|
echo ""
|
||
|
|
|
||
|
|
# Create work directories
|
||
|
|
mkdir -p "$WORK_DIR"
|
||
|
|
mkdir -p "$OUTPUT_DIR"
|
||
|
|
|
||
|
|
# Download Alpine base ISO if not exists
|
||
|
|
BASE_ISO="$WORK_DIR/alpine-base.iso"
|
||
|
|
if [ ! -f "$BASE_ISO" ]; then
|
||
|
|
echo "📥 Downloading Alpine base ISO..."
|
||
|
|
curl -L -o "$BASE_ISO" "$ISO_URL" || {
|
||
|
|
echo "❌ Failed to download Alpine ISO"
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
echo "✅ Downloaded base ISO"
|
||
|
|
else
|
||
|
|
echo "✅ Using cached base ISO"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Extract ISO contents
|
||
|
|
echo "📦 Extracting ISO contents..."
|
||
|
|
CUSTOM_DIR="$WORK_DIR/custom"
|
||
|
|
rm -rf "$CUSTOM_DIR"
|
||
|
|
mkdir -p "$CUSTOM_DIR"
|
||
|
|
|
||
|
|
# Use 7zip to extract ISO (works on both macOS and Linux)
|
||
|
|
if command -v 7z >/dev/null 2>&1; then
|
||
|
|
7z x "$BASE_ISO" -o"$CUSTOM_DIR" -y >/dev/null || {
|
||
|
|
echo "❌ Failed to extract ISO with 7zip"
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||
|
|
# Fallback: Try mounting on macOS
|
||
|
|
MOUNT_POINT="$WORK_DIR/mnt"
|
||
|
|
mkdir -p "$MOUNT_POINT"
|
||
|
|
hdiutil attach "$BASE_ISO" -mountpoint "$MOUNT_POINT" -readonly && {
|
||
|
|
rsync -a "$MOUNT_POINT"/ "$CUSTOM_DIR"/
|
||
|
|
hdiutil detach "$MOUNT_POINT"
|
||
|
|
} || {
|
||
|
|
echo "❌ Failed to extract ISO. Install p7zip: brew install p7zip"
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
else
|
||
|
|
# On Linux, mount the ISO
|
||
|
|
MOUNT_POINT="$WORK_DIR/mnt"
|
||
|
|
mkdir -p "$MOUNT_POINT"
|
||
|
|
sudo mount -o loop "$BASE_ISO" "$MOUNT_POINT" && {
|
||
|
|
cp -a "$MOUNT_POINT"/* "$CUSTOM_DIR"/
|
||
|
|
sudo umount "$MOUNT_POINT"
|
||
|
|
} || {
|
||
|
|
echo "❌ Failed to mount ISO"
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo "✅ ISO contents extracted"
|
||
|
|
|
||
|
|
# Add custom packages
|
||
|
|
echo "📦 Adding custom packages..."
|
||
|
|
APKS_DIR="$CUSTOM_DIR/apks/$(uname -m)"
|
||
|
|
mkdir -p "$APKS_DIR"
|
||
|
|
|
||
|
|
# Create a custom apk repository file
|
||
|
|
cat > "$CUSTOM_DIR/archipelago.list" <<'EOF'
|
||
|
|
# Archipelago custom packages
|
||
|
|
# These will be installed during setup-alpine or via apk add
|
||
|
|
|
||
|
|
# Container runtime
|
||
|
|
podman
|
||
|
|
crun
|
||
|
|
fuse-overlayfs
|
||
|
|
slirp4netns
|
||
|
|
|
||
|
|
# Web server
|
||
|
|
nginx
|
||
|
|
|
||
|
|
# SSH server
|
||
|
|
openssh
|
||
|
|
|
||
|
|
# Networking
|
||
|
|
iptables
|
||
|
|
iproute2
|
||
|
|
EOF
|
||
|
|
|
||
|
|
# Create automated installation script
|
||
|
|
echo "📝 Creating installation script..."
|
||
|
|
mkdir -p "$CUSTOM_DIR/archipelago"
|
||
|
|
cat > "$CUSTOM_DIR/archipelago/install.sh" <<'INSTALL_EOF'
|
||
|
|
#!/bin/sh
|
||
|
|
# Archipelago automated installation script
|
||
|
|
|
||
|
|
set -e
|
||
|
|
|
||
|
|
echo "🏝️ Installing Archipelago Bitcoin Node OS"
|
||
|
|
echo ""
|
||
|
|
|
||
|
|
# Install required packages
|
||
|
|
echo "📦 Installing packages..."
|
||
|
|
apk add --no-cache \
|
||
|
|
podman \
|
||
|
|
crun \
|
||
|
|
fuse-overlayfs \
|
||
|
|
slirp4netns \
|
||
|
|
nginx \
|
||
|
|
openssh \
|
||
|
|
iptables \
|
||
|
|
iproute2 \
|
||
|
|
bash \
|
||
|
|
curl
|
||
|
|
|
||
|
|
# Create archipelago user
|
||
|
|
echo "👤 Creating archipelago user..."
|
||
|
|
if ! id archipelago >/dev/null 2>&1; then
|
||
|
|
adduser -D -s /bin/bash archipelago
|
||
|
|
echo "archipelago:archipelago" | chpasswd
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Setup Podman for archipelago user
|
||
|
|
echo "🐳 Setting up Podman..."
|
||
|
|
mkdir -p /home/archipelago/.config/containers
|
||
|
|
chown -R archipelago:archipelago /home/archipelago
|
||
|
|
|
||
|
|
# Create data directories
|
||
|
|
echo "📁 Creating data directories..."
|
||
|
|
mkdir -p /var/lib/archipelago/{apps,secrets,logs,backups}
|
||
|
|
chown -R archipelago:archipelago /var/lib/archipelago
|
||
|
|
|
||
|
|
# Install Archipelago backend if available
|
||
|
|
if [ -f /media/cdrom/archipelago/bin/archipelago ]; then
|
||
|
|
echo "🦀 Installing Archipelago backend..."
|
||
|
|
mkdir -p /usr/local/bin
|
||
|
|
cp /media/cdrom/archipelago/bin/archipelago /usr/local/bin/
|
||
|
|
chmod +x /usr/local/bin/archipelago
|
||
|
|
echo "✅ Backend installed"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Install Archipelago frontend if available
|
||
|
|
if [ -d /media/cdrom/archipelago/web ]; then
|
||
|
|
echo "🎨 Installing Archipelago web UI..."
|
||
|
|
mkdir -p /usr/share/archipelago/web
|
||
|
|
cp -r /media/cdrom/archipelago/web/* /usr/share/archipelago/web/
|
||
|
|
|
||
|
|
# Configure nginx
|
||
|
|
cat > /etc/nginx/http.d/archipelago.conf <<'NGINX_EOF'
|
||
|
|
server {
|
||
|
|
listen 8100;
|
||
|
|
server_name _;
|
||
|
|
|
||
|
|
root /usr/share/archipelago/web;
|
||
|
|
index index.html;
|
||
|
|
|
||
|
|
location / {
|
||
|
|
try_files $uri $uri/ /index.html;
|
||
|
|
}
|
||
|
|
|
||
|
|
# Proxy API requests to backend
|
||
|
|
location /api/ {
|
||
|
|
proxy_pass http://127.0.0.1:8101/;
|
||
|
|
proxy_http_version 1.1;
|
||
|
|
proxy_set_header Upgrade $http_upgrade;
|
||
|
|
proxy_set_header Connection 'upgrade';
|
||
|
|
proxy_set_header Host $host;
|
||
|
|
proxy_cache_bypass $http_upgrade;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
NGINX_EOF
|
||
|
|
|
||
|
|
rc-update add nginx default
|
||
|
|
echo "✅ Web UI installed"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Create Archipelago service
|
||
|
|
if [ -f /usr/local/bin/archipelago ]; then
|
||
|
|
echo "⚙️ Creating Archipelago service..."
|
||
|
|
cat > /etc/init.d/archipelago <<'SERVICE_EOF'
|
||
|
|
#!/sbin/openrc-run
|
||
|
|
|
||
|
|
name="Archipelago"
|
||
|
|
description="Archipelago Bitcoin Node OS Backend"
|
||
|
|
command="/usr/local/bin/archipelago"
|
||
|
|
command_background=true
|
||
|
|
pidfile="/run/archipelago.pid"
|
||
|
|
|
||
|
|
depend() {
|
||
|
|
need net
|
||
|
|
after networking
|
||
|
|
}
|
||
|
|
SERVICE_EOF
|
||
|
|
|
||
|
|
chmod +x /etc/init.d/archipelago
|
||
|
|
rc-update add archipelago default
|
||
|
|
echo "✅ Archipelago service created"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Install app manifests if available
|
||
|
|
if [ -d /media/cdrom/archipelago/apps ]; then
|
||
|
|
echo "📦 Installing app manifests..."
|
||
|
|
mkdir -p /var/lib/archipelago/manifests
|
||
|
|
cp -r /media/cdrom/archipelago/apps/* /var/lib/archipelago/manifests/
|
||
|
|
chown -R archipelago:archipelago /var/lib/archipelago/manifests
|
||
|
|
echo "✅ App manifests installed"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Configure networking (DHCP)
|
||
|
|
echo "🌐 Configuring network..."
|
||
|
|
cat > /etc/network/interfaces <<'NET_EOF'
|
||
|
|
auto lo
|
||
|
|
iface lo inet loopback
|
||
|
|
|
||
|
|
auto eth0
|
||
|
|
iface eth0 inet dhcp
|
||
|
|
|
||
|
|
auto enp0s3
|
||
|
|
iface enp0s3 inet dhcp
|
||
|
|
|
||
|
|
auto enp0s25
|
||
|
|
iface enp0s25 inet dhcp
|
||
|
|
NET_EOF
|
||
|
|
|
||
|
|
# Enable services
|
||
|
|
echo "⚙️ Enabling services..."
|
||
|
|
rc-update add networking default
|
||
|
|
rc-update add sshd default
|
||
|
|
rc-update add local default
|
||
|
|
|
||
|
|
# Set hostname
|
||
|
|
echo "archipelago" > /etc/hostname
|
||
|
|
|
||
|
|
# Configure SSH
|
||
|
|
echo "🔐 Configuring SSH..."
|
||
|
|
sed -i 's/#PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config
|
||
|
|
|
||
|
|
# Create welcome message
|
||
|
|
cat > /etc/motd <<'MOTD_EOF'
|
||
|
|
|
||
|
|
╔═══════════════════════════════════════════════════════════╗
|
||
|
|
║ ║
|
||
|
|
║ 🏝️ ARCHIPELAGO BITCOIN NODE OS ║
|
||
|
|
║ ║
|
||
|
|
║ A sovereign computing platform built on Alpine Linux ║
|
||
|
|
║ ║
|
||
|
|
╚═══════════════════════════════════════════════════════════╝
|
||
|
|
|
||
|
|
Default user: archipelago
|
||
|
|
Default pass: archipelago (CHANGE THIS IMMEDIATELY!)
|
||
|
|
|
||
|
|
To access the web UI:
|
||
|
|
http://$(hostname -i):8100
|
||
|
|
|
||
|
|
To start Archipelago:
|
||
|
|
sudo systemctl start archipelago
|
||
|
|
|
||
|
|
Documentation: /usr/share/doc/archipelago/
|
||
|
|
|
||
|
|
MOTD_EOF
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo "✅ Archipelago installation complete!"
|
||
|
|
echo ""
|
||
|
|
echo "Next steps:"
|
||
|
|
echo " 1. Reboot the system"
|
||
|
|
echo " 2. Login with user: archipelago, pass: archipelago"
|
||
|
|
echo " 3. Change the default password: passwd"
|
||
|
|
echo " 4. Access web UI at http://<your-ip>:8100"
|
||
|
|
echo ""
|
||
|
|
|
||
|
|
INSTALL_EOF
|
||
|
|
|
||
|
|
chmod +x "$CUSTOM_DIR/archipelago/install.sh"
|
||
|
|
|
||
|
|
# Create README
|
||
|
|
cat > "$CUSTOM_DIR/archipelago/README.txt" <<'README_EOF'
|
||
|
|
ARCHIPELAGO BITCOIN NODE OS
|
||
|
|
===========================
|
||
|
|
|
||
|
|
This is a customized Alpine Linux ISO with Archipelago packages.
|
||
|
|
|
||
|
|
INSTALLATION:
|
||
|
|
1. Boot from this USB/ISO
|
||
|
|
2. Login as root (no password)
|
||
|
|
3. Run: setup-alpine
|
||
|
|
4. Follow the prompts to install Alpine
|
||
|
|
5. After installation, run: sh /media/*/archipelago/install.sh
|
||
|
|
6. Reboot
|
||
|
|
|
||
|
|
QUICK INSTALL:
|
||
|
|
For automated installation on dedicated hardware:
|
||
|
|
1. Boot from USB/ISO
|
||
|
|
2. Login as root
|
||
|
|
3. Run: sh /media/*/archipelago/install.sh
|
||
|
|
4. This will set up Alpine + Archipelago automatically
|
||
|
|
|
||
|
|
For more information, visit:
|
||
|
|
https://github.com/your-repo/archipelago
|
||
|
|
|
||
|
|
README_EOF
|
||
|
|
|
||
|
|
# Modify boot configuration to show Archipelago branding
|
||
|
|
if [ -f "$CUSTOM_DIR/boot/grub/grub.cfg" ]; then
|
||
|
|
echo "⚙️ Customizing boot menu..."
|
||
|
|
sed -i.bak 's/Alpine Linux/Archipelago Bitcoin Node OS/g' "$CUSTOM_DIR/boot/grub/grub.cfg" 2>/dev/null || true
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [ -f "$CUSTOM_DIR/boot/syslinux/syslinux.cfg" ]; then
|
||
|
|
sed -i.bak 's/Alpine Linux/Archipelago Bitcoin Node OS/g' "$CUSTOM_DIR/boot/syslinux/syslinux.cfg" 2>/dev/null || true
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Include Archipelago backend if available
|
||
|
|
if [ -n "$INCLUDE_BACKEND" ] && [ -f "$INCLUDE_BACKEND" ]; then
|
||
|
|
echo "🦀 Including Archipelago backend..."
|
||
|
|
mkdir -p "$CUSTOM_DIR/archipelago/bin"
|
||
|
|
cp "$INCLUDE_BACKEND" "$CUSTOM_DIR/archipelago/bin/"
|
||
|
|
chmod +x "$CUSTOM_DIR/archipelago/bin/archipelago"
|
||
|
|
echo "✅ Backend included: $(du -h "$INCLUDE_BACKEND" | cut -f1)"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Include Archipelago frontend if available
|
||
|
|
if [ -n "$INCLUDE_FRONTEND" ] && [ -d "$INCLUDE_FRONTEND" ]; then
|
||
|
|
echo "🎨 Including Archipelago frontend..."
|
||
|
|
mkdir -p "$CUSTOM_DIR/archipelago/web"
|
||
|
|
cp -r "$INCLUDE_FRONTEND"/* "$CUSTOM_DIR/archipelago/web/"
|
||
|
|
echo "✅ Frontend included"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Include app manifests if available
|
||
|
|
APPS_DIR="$(dirname "$0")/../apps"
|
||
|
|
if [ -d "$APPS_DIR" ]; then
|
||
|
|
echo "📦 Including app manifests..."
|
||
|
|
mkdir -p "$CUSTOM_DIR/archipelago/apps"
|
||
|
|
# Copy all app directories with manifest.yml files
|
||
|
|
find "$APPS_DIR" -maxdepth 1 -type d -exec test -f {}/manifest.yml \; -exec cp -r {} "$CUSTOM_DIR/archipelago/apps/" \;
|
||
|
|
APP_COUNT=$(find "$CUSTOM_DIR/archipelago/apps" -name "manifest.yml" | wc -l | tr -d ' ')
|
||
|
|
echo "✅ Included $APP_COUNT app manifests"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Create the ISO
|
||
|
|
OUTPUT_ISO="$OUTPUT_DIR/archipelago-${ALPINE_VERSION}-${ARCH}.iso"
|
||
|
|
echo ""
|
||
|
|
echo "🔥 Creating final ISO..."
|
||
|
|
|
||
|
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||
|
|
# On macOS, use xorriso for hybrid BIOS+UEFI bootable ISO
|
||
|
|
if command -v xorriso >/dev/null 2>&1; then
|
||
|
|
# Try dual-boot (BIOS + UEFI) if EFI bootloader exists
|
||
|
|
if [ -f "$CUSTOM_DIR/boot/grub/grub.cfg" ] || [ -d "$CUSTOM_DIR/efi" ] || [ -d "$CUSTOM_DIR/EFI" ]; then
|
||
|
|
echo "🔄 Creating hybrid BIOS+UEFI bootable ISO..."
|
||
|
|
xorriso -as mkisofs -o "$OUTPUT_ISO" \
|
||
|
|
-volid "ARCHIPELAGO" \
|
||
|
|
-J -R \
|
||
|
|
-c boot/syslinux/boot.cat \
|
||
|
|
-b boot/syslinux/isolinux.bin \
|
||
|
|
-no-emul-boot -boot-load-size 4 -boot-info-table \
|
||
|
|
-eltorito-alt-boot \
|
||
|
|
-e boot/grub/efi.img \
|
||
|
|
-no-emul-boot \
|
||
|
|
-isohybrid-gpt-basdat \
|
||
|
|
-isohybrid-mbr "$CUSTOM_DIR/boot/syslinux/isohdpfx.bin" \
|
||
|
|
"$CUSTOM_DIR" 2>&1 | grep -v "xorriso : UPDATE" || {
|
||
|
|
echo "⚠️ Dual-boot failed, trying BIOS-only..."
|
||
|
|
xorriso -as mkisofs -o "$OUTPUT_ISO" \
|
||
|
|
-volid "ARCHIPELAGO" \
|
||
|
|
-J -R \
|
||
|
|
-c boot/syslinux/boot.cat \
|
||
|
|
-b boot/syslinux/isolinux.bin \
|
||
|
|
-no-emul-boot -boot-load-size 4 -boot-info-table \
|
||
|
|
-isohybrid-mbr "$CUSTOM_DIR/boot/syslinux/isohdpfx.bin" \
|
||
|
|
"$CUSTOM_DIR" 2>&1 | grep -v "xorriso : UPDATE"
|
||
|
|
}
|
||
|
|
else
|
||
|
|
echo "🔄 Creating BIOS-only bootable ISO..."
|
||
|
|
xorriso -as mkisofs -o "$OUTPUT_ISO" \
|
||
|
|
-volid "ARCHIPELAGO" \
|
||
|
|
-J -R \
|
||
|
|
-c boot/syslinux/boot.cat \
|
||
|
|
-b boot/syslinux/isolinux.bin \
|
||
|
|
-no-emul-boot -boot-load-size 4 -boot-info-table \
|
||
|
|
-isohybrid-mbr "$CUSTOM_DIR/boot/syslinux/isohdpfx.bin" \
|
||
|
|
"$CUSTOM_DIR" 2>&1 | grep -v "xorriso : UPDATE" || true
|
||
|
|
|
||
|
|
# Check if ISO was created
|
||
|
|
if [ ! -f "$OUTPUT_ISO" ]; then
|
||
|
|
echo "⚠️ xorriso failed, trying without isohybrid..."
|
||
|
|
xorriso -as mkisofs -o "$OUTPUT_ISO" \
|
||
|
|
-volid "ARCHIPELAGO" \
|
||
|
|
-J -R \
|
||
|
|
-c boot/syslinux/boot.cat \
|
||
|
|
-b boot/syslinux/isolinux.bin \
|
||
|
|
-no-emul-boot -boot-load-size 4 -boot-info-table \
|
||
|
|
"$CUSTOM_DIR" 2>&1 | grep -v "xorriso : UPDATE"
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
else
|
||
|
|
# Fallback to hdiutil
|
||
|
|
hdiutil makehybrid -o "$OUTPUT_ISO" "$CUSTOM_DIR" \
|
||
|
|
-iso -joliet -default-volume-name "ARCHIPELAGO" || {
|
||
|
|
echo "❌ Failed to create ISO"
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
fi
|
||
|
|
else
|
||
|
|
# On Linux, use genisoimage or xorriso
|
||
|
|
if command -v genisoimage >/dev/null 2>&1; then
|
||
|
|
genisoimage -o "$OUTPUT_ISO" \
|
||
|
|
-b boot/syslinux/isolinux.bin \
|
||
|
|
-c boot/syslinux/boot.cat \
|
||
|
|
-no-emul-boot -boot-load-size 4 -boot-info-table \
|
||
|
|
-J -R -V "ARCHIPELAGO" \
|
||
|
|
"$CUSTOM_DIR"
|
||
|
|
elif command -v xorriso >/dev/null 2>&1; then
|
||
|
|
xorriso -as mkisofs -o "$OUTPUT_ISO" \
|
||
|
|
-b boot/syslinux/isolinux.bin \
|
||
|
|
-c boot/syslinux/boot.cat \
|
||
|
|
-no-emul-boot -boot-load-size 4 -boot-info-table \
|
||
|
|
-J -R -V "ARCHIPELAGO" \
|
||
|
|
"$CUSTOM_DIR"
|
||
|
|
else
|
||
|
|
echo "❌ No ISO creation tool found (need genisoimage or xorriso)"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Make bootable (add isohybrid for USB boot)
|
||
|
|
if command -v isohybrid >/dev/null 2>&1 && [[ "$OSTYPE" != "darwin"* ]]; then
|
||
|
|
isohybrid "$OUTPUT_ISO"
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo ""
|
||
|
|
echo "✅ ISO created successfully!"
|
||
|
|
echo ""
|
||
|
|
echo "📀 Output: $OUTPUT_ISO"
|
||
|
|
echo " Size: $(du -h "$OUTPUT_ISO" | cut -f1)"
|
||
|
|
echo ""
|
||
|
|
echo "📝 To create a bootable USB:"
|
||
|
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||
|
|
echo " 1. Insert USB drive"
|
||
|
|
echo " 2. Find device: diskutil list"
|
||
|
|
echo " 3. Unmount: diskutil unmountDisk /dev/diskN"
|
||
|
|
echo " 4. Write: sudo dd if=$OUTPUT_ISO of=/dev/rdiskN bs=1m"
|
||
|
|
else
|
||
|
|
echo " sudo dd if=$OUTPUT_ISO of=/dev/sdX bs=4M status=progress"
|
||
|
|
fi
|
||
|
|
echo ""
|