update
This commit is contained in:
319
isobuild/Dockerfile
Normal file
319
isobuild/Dockerfile
Normal file
@@ -0,0 +1,319 @@
|
||||
# EcoOS ISO Builder
|
||||
# Build from eco_os directory:
|
||||
# docker build -t ecoos-builder -f isobuild/Dockerfile .
|
||||
# docker run --privileged -v $(pwd)/isobuild/output:/output ecoos-builder
|
||||
|
||||
FROM ubuntu:24.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install build dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
live-build \
|
||||
debootstrap \
|
||||
xorriso \
|
||||
squashfs-tools \
|
||||
grub-efi-amd64-bin \
|
||||
grub-efi-amd64-signed \
|
||||
grub-pc-bin \
|
||||
shim-signed \
|
||||
mtools \
|
||||
dosfstools \
|
||||
syslinux-utils \
|
||||
syslinux \
|
||||
syslinux-common \
|
||||
isolinux \
|
||||
curl \
|
||||
unzip \
|
||||
git \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& ln -sf /usr/bin/isohybrid /usr/local/bin/isohybrid 2>/dev/null || true
|
||||
|
||||
# Install Deno
|
||||
RUN curl -fsSL https://deno.land/install.sh | DENO_INSTALL=/usr/local sh
|
||||
|
||||
# Create build directory
|
||||
WORKDIR /build
|
||||
|
||||
# Copy isobuild configuration
|
||||
COPY isobuild/config/ /build/config/
|
||||
COPY isobuild/scripts/ /build/scripts/
|
||||
COPY isobuild/ts/ /build/ts/
|
||||
COPY isobuild/deno.json /build/
|
||||
COPY isobuild/mod.ts /build/
|
||||
|
||||
# Copy hooks to enable services (already in config/, but put in separate dir for build script)
|
||||
COPY isobuild/config/hooks/ /build/hooks/
|
||||
|
||||
# Copy daemon source (for bundling)
|
||||
COPY ecoos_daemon/ /daemon/
|
||||
|
||||
# Bundle the daemon
|
||||
RUN cd /daemon && deno compile --allow-all --output /build/daemon-bundle/eco-daemon mod.ts
|
||||
|
||||
# Make scripts executable
|
||||
RUN chmod +x /build/scripts/*.sh
|
||||
|
||||
# Create dummy isohybrid for UEFI-only builds (real isohybrid needs BIOS boot record)
|
||||
RUN echo '#!/bin/sh' > /usr/local/bin/isohybrid && \
|
||||
echo 'echo "Skipping isohybrid (UEFI-only build)"' >> /usr/local/bin/isohybrid && \
|
||||
echo 'exit 0' >> /usr/local/bin/isohybrid && \
|
||||
chmod +x /usr/local/bin/isohybrid
|
||||
|
||||
# Build script
|
||||
COPY <<'EOF' /build/docker-build.sh
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
export PATH="/usr/local/bin:/usr/bin:/usr/sbin:/bin:/sbin:$PATH"
|
||||
|
||||
echo "=== EcoOS ISO Builder (Docker) ==="
|
||||
|
||||
cd /build
|
||||
|
||||
# Initialize live-build - UEFI only (no syslinux/BIOS)
|
||||
# Using German mirror for faster/more stable downloads
|
||||
lb config \
|
||||
--architectures amd64 \
|
||||
--distribution noble \
|
||||
--archive-areas "main restricted universe multiverse" \
|
||||
--mirror-bootstrap "http://ftp.halifax.rwth-aachen.de/ubuntu/" \
|
||||
--mirror-chroot "http://ftp.halifax.rwth-aachen.de/ubuntu/" \
|
||||
--mirror-chroot-security "http://ftp.halifax.rwth-aachen.de/ubuntu/" \
|
||||
--mirror-binary "http://ftp.halifax.rwth-aachen.de/ubuntu/" \
|
||||
--mirror-binary-security "http://ftp.halifax.rwth-aachen.de/ubuntu/" \
|
||||
--binary-images iso-hybrid \
|
||||
--debian-installer false \
|
||||
--memtest none \
|
||||
--bootloader grub-efi \
|
||||
--iso-application "EcoOS" \
|
||||
--iso-publisher "EcoBridge" \
|
||||
--iso-volume "EcoOS"
|
||||
|
||||
# Copy package lists
|
||||
cp /build/config/live-build/package-lists/*.list.chroot config/package-lists/
|
||||
|
||||
# Prepare includes.chroot
|
||||
mkdir -p config/includes.chroot/opt/eco/bin
|
||||
mkdir -p config/includes.chroot/opt/eco/daemon
|
||||
mkdir -p config/includes.chroot/etc/systemd/system
|
||||
|
||||
# Copy daemon bundle
|
||||
cp /build/daemon-bundle/eco-daemon config/includes.chroot/opt/eco/bin/
|
||||
chmod +x config/includes.chroot/opt/eco/bin/eco-daemon
|
||||
|
||||
# Create dummy isohybrid in chroot (UEFI-only, no BIOS boot record)
|
||||
mkdir -p config/includes.chroot/usr/bin
|
||||
cat > config/includes.chroot/usr/bin/isohybrid << 'ISOHYBRID'
|
||||
#!/bin/sh
|
||||
echo "Skipping isohybrid (UEFI-only build)"
|
||||
exit 0
|
||||
ISOHYBRID
|
||||
chmod +x config/includes.chroot/usr/bin/isohybrid
|
||||
|
||||
# Copy systemd services
|
||||
cp /build/config/systemd/eco-daemon.service config/includes.chroot/etc/systemd/system/
|
||||
|
||||
# Copy installer (files are already in config/includes.chroot via COPY)
|
||||
chmod +x config/includes.chroot/opt/eco/installer/install.sh 2>/dev/null || true
|
||||
|
||||
# Copy hooks to enable services
|
||||
mkdir -p config/hooks/normal
|
||||
cp /build/hooks/normal/*.hook.chroot config/hooks/normal/
|
||||
chmod +x config/hooks/normal/*.hook.chroot
|
||||
|
||||
# Copy autoinstall config
|
||||
mkdir -p config/includes.binary/autoinstall
|
||||
cp /build/config/autoinstall/user-data config/includes.binary/autoinstall/
|
||||
touch config/includes.binary/autoinstall/meta-data
|
||||
|
||||
# Prepare EFI boot files in includes.binary
|
||||
echo "Preparing EFI boot structure..."
|
||||
mkdir -p config/includes.binary/EFI/BOOT
|
||||
mkdir -p config/includes.binary/boot/grub
|
||||
|
||||
# Copy signed EFI files from host (installed in Docker image)
|
||||
cp /usr/lib/shim/shimx64.efi.signed.latest config/includes.binary/EFI/BOOT/BOOTX64.EFI || \
|
||||
cp /usr/lib/shim/shimx64.efi.signed config/includes.binary/EFI/BOOT/BOOTX64.EFI || \
|
||||
cp /usr/lib/shim/shimx64.efi config/includes.binary/EFI/BOOT/BOOTX64.EFI || true
|
||||
|
||||
cp /usr/lib/grub/x86_64-efi-signed/grubx64.efi.signed config/includes.binary/EFI/BOOT/grubx64.efi || \
|
||||
cp /usr/lib/grub/x86_64-efi/grubx64.efi config/includes.binary/EFI/BOOT/grubx64.efi || true
|
||||
|
||||
# Also provide mmx64.efi for some UEFI implementations
|
||||
if [ -f config/includes.binary/EFI/BOOT/grubx64.efi ]; then
|
||||
cp config/includes.binary/EFI/BOOT/grubx64.efi config/includes.binary/EFI/BOOT/mmx64.efi
|
||||
fi
|
||||
|
||||
# Create grub.cfg for live boot with installer option
|
||||
cat > config/includes.binary/boot/grub/grub.cfg << 'GRUBCFG'
|
||||
set default=0
|
||||
set timeout=10
|
||||
|
||||
insmod part_gpt
|
||||
insmod fat
|
||||
insmod efi_gop
|
||||
insmod efi_uga
|
||||
|
||||
menuentry "Install EcoOS (auto-selects in 10s)" {
|
||||
linux /casper/vmlinuz boot=casper quiet splash ecoos_install=1 ---
|
||||
initrd /casper/initrd
|
||||
}
|
||||
|
||||
menuentry "EcoOS Live (Try without installing)" {
|
||||
linux /casper/vmlinuz boot=casper quiet splash ---
|
||||
initrd /casper/initrd
|
||||
}
|
||||
|
||||
menuentry "EcoOS Live (Safe Mode)" {
|
||||
linux /casper/vmlinuz boot=casper nomodeset ---
|
||||
initrd /casper/initrd
|
||||
}
|
||||
GRUBCFG
|
||||
|
||||
# Also put grub.cfg in EFI/BOOT for fallback
|
||||
cp config/includes.binary/boot/grub/grub.cfg config/includes.binary/EFI/BOOT/grub.cfg
|
||||
|
||||
# Build ISO - use individual lb stages to control the process
|
||||
lb bootstrap
|
||||
lb chroot
|
||||
|
||||
# Try lb binary, but continue even if isohybrid fails
|
||||
lb binary || {
|
||||
echo "lb binary had errors, checking if ISO was created anyway..."
|
||||
if ls /build/*.iso 2>/dev/null; then
|
||||
echo "ISO exists despite errors, continuing..."
|
||||
else
|
||||
echo "No ISO found, build truly failed"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if EFI was created properly
|
||||
echo "Checking binary directory for EFI..."
|
||||
ls -la binary/EFI/BOOT/ 2>/dev/null || echo "EFI/BOOT not found in binary dir"
|
||||
|
||||
# Find the ISO file
|
||||
echo "Searching for ISO file..."
|
||||
find /build -name "*.iso" -type f 2>/dev/null
|
||||
ls -la /build/*.iso 2>/dev/null || true
|
||||
|
||||
ISO_FILE=$(find /build -name "*.iso" -type f 2>/dev/null | head -1)
|
||||
if [ -z "$ISO_FILE" ]; then
|
||||
echo "ERROR: No ISO file found in build directory"
|
||||
echo "Listing /build contents:"
|
||||
ls -la /build/
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found ISO: $ISO_FILE"
|
||||
|
||||
# Always create proper EFI boot image and rebuild ISO
|
||||
echo "Creating UEFI-bootable ISO..."
|
||||
|
||||
# Extract ISO contents
|
||||
mkdir -p /tmp/iso_extract
|
||||
xorriso -osirrox on -indev "$ISO_FILE" -extract / /tmp/iso_extract
|
||||
|
||||
# Find the actual kernel and initrd names
|
||||
VMLINUZ=$(ls /tmp/iso_extract/casper/vmlinuz* 2>/dev/null | head -1 | xargs basename)
|
||||
INITRD=$(ls /tmp/iso_extract/casper/initrd* 2>/dev/null | head -1 | xargs basename)
|
||||
|
||||
echo "Found kernel: $VMLINUZ, initrd: $INITRD"
|
||||
|
||||
# Ensure EFI structure exists with proper files
|
||||
mkdir -p /tmp/iso_extract/EFI/BOOT
|
||||
mkdir -p /tmp/iso_extract/boot/grub
|
||||
|
||||
# Copy EFI files from host
|
||||
cp /usr/lib/shim/shimx64.efi.signed.latest /tmp/iso_extract/EFI/BOOT/BOOTX64.EFI 2>/dev/null || \
|
||||
cp /usr/lib/shim/shimx64.efi.signed /tmp/iso_extract/EFI/BOOT/BOOTX64.EFI 2>/dev/null || \
|
||||
cp /usr/lib/shim/shimx64.efi /tmp/iso_extract/EFI/BOOT/BOOTX64.EFI 2>/dev/null || true
|
||||
|
||||
cp /usr/lib/grub/x86_64-efi-signed/grubx64.efi.signed /tmp/iso_extract/EFI/BOOT/grubx64.efi 2>/dev/null || \
|
||||
cp /usr/lib/grub/x86_64-efi/grubx64.efi /tmp/iso_extract/EFI/BOOT/grubx64.efi 2>/dev/null || true
|
||||
|
||||
# Copy mmx64.efi for secure boot compatibility
|
||||
if [ -f /tmp/iso_extract/EFI/BOOT/grubx64.efi ]; then
|
||||
cp /tmp/iso_extract/EFI/BOOT/grubx64.efi /tmp/iso_extract/EFI/BOOT/mmx64.efi
|
||||
fi
|
||||
|
||||
# Create grub.cfg with correct filenames and installer option
|
||||
cat > /tmp/iso_extract/boot/grub/grub.cfg << GRUBCFG2
|
||||
set default=0
|
||||
set timeout=10
|
||||
|
||||
insmod part_gpt
|
||||
insmod fat
|
||||
insmod efi_gop
|
||||
insmod efi_uga
|
||||
|
||||
menuentry "Install EcoOS (auto-selects in 10s)" {
|
||||
linux /casper/${VMLINUZ} boot=casper quiet splash ecoos_install=1 ---
|
||||
initrd /casper/${INITRD}
|
||||
}
|
||||
|
||||
menuentry "EcoOS Live (Try without installing)" {
|
||||
linux /casper/${VMLINUZ} boot=casper quiet splash ---
|
||||
initrd /casper/${INITRD}
|
||||
}
|
||||
|
||||
menuentry "EcoOS Live (Safe Mode)" {
|
||||
linux /casper/${VMLINUZ} boot=casper nomodeset ---
|
||||
initrd /casper/${INITRD}
|
||||
}
|
||||
GRUBCFG2
|
||||
|
||||
cp /tmp/iso_extract/boot/grub/grub.cfg /tmp/iso_extract/EFI/BOOT/grub.cfg
|
||||
|
||||
# Create EFI boot image (FAT filesystem for UEFI El Torito boot)
|
||||
echo "Creating EFI boot image..."
|
||||
dd if=/dev/zero of=/tmp/efi.img bs=1M count=10
|
||||
mkfs.fat -F 12 /tmp/efi.img
|
||||
mmd -i /tmp/efi.img ::/EFI
|
||||
mmd -i /tmp/efi.img ::/EFI/BOOT
|
||||
mcopy -i /tmp/efi.img /tmp/iso_extract/EFI/BOOT/BOOTX64.EFI ::/EFI/BOOT/
|
||||
mcopy -i /tmp/efi.img /tmp/iso_extract/EFI/BOOT/grubx64.efi ::/EFI/BOOT/ 2>/dev/null || true
|
||||
mcopy -i /tmp/efi.img /tmp/iso_extract/EFI/BOOT/mmx64.efi ::/EFI/BOOT/ 2>/dev/null || true
|
||||
mcopy -i /tmp/efi.img /tmp/iso_extract/EFI/BOOT/grub.cfg ::/EFI/BOOT/
|
||||
|
||||
# Rebuild ISO with EFI boot support (UEFI-only, no BIOS boot)
|
||||
echo "Rebuilding ISO with UEFI boot support..."
|
||||
xorriso -as mkisofs \
|
||||
-r -V "EcoOS" \
|
||||
-o /tmp/ecoos-efi.iso \
|
||||
-J -joliet-long \
|
||||
-eltorito-alt-boot \
|
||||
-e --interval:appended_partition_2:all:: \
|
||||
-no-emul-boot -isohybrid-gpt-basdat \
|
||||
-append_partition 2 0xef /tmp/efi.img \
|
||||
/tmp/iso_extract
|
||||
|
||||
if [ -f /tmp/ecoos-efi.iso ]; then
|
||||
ISO_FILE=/tmp/ecoos-efi.iso
|
||||
echo "Created UEFI-bootable ISO: $ISO_FILE"
|
||||
else
|
||||
echo "ERROR: Failed to create EFI ISO"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf /tmp/iso_extract
|
||||
|
||||
# Copy to output
|
||||
mkdir -p /output
|
||||
cp "$ISO_FILE" /output/ecoos.iso
|
||||
|
||||
# Final verification
|
||||
echo ""
|
||||
echo "=== Final ISO EFI check ==="
|
||||
xorriso -indev /output/ecoos.iso -find / -maxdepth 2 -type d 2>/dev/null || true
|
||||
|
||||
echo ""
|
||||
echo "=== Build Complete ==="
|
||||
echo "ISO: /output/ecoos.iso"
|
||||
ls -lh /output/ecoos.iso
|
||||
EOF
|
||||
|
||||
RUN chmod +x /build/docker-build.sh
|
||||
|
||||
CMD ["/build/docker-build.sh"]
|
||||
75
isobuild/config/autoinstall/user-data
Normal file
75
isobuild/config/autoinstall/user-data
Normal file
@@ -0,0 +1,75 @@
|
||||
#cloud-config
|
||||
autoinstall:
|
||||
version: 1
|
||||
|
||||
# Locale and keyboard
|
||||
locale: en_US.UTF-8
|
||||
keyboard:
|
||||
layout: us
|
||||
|
||||
# Network configuration - DHCP on all interfaces
|
||||
network:
|
||||
network:
|
||||
version: 2
|
||||
ethernets:
|
||||
id0:
|
||||
match:
|
||||
driver: "*"
|
||||
dhcp4: true
|
||||
|
||||
# Storage - use entire disk
|
||||
storage:
|
||||
layout:
|
||||
name: direct
|
||||
|
||||
# Identity - create ecouser
|
||||
identity:
|
||||
hostname: ecoos
|
||||
username: ecouser
|
||||
# Password: ecouser (hashed with mkpasswd -m sha-512)
|
||||
password: "$6$rounds=4096$randomsalt$n8Y5TqMKJZ5kM3LN0Y5fZ5Y5kM3LN0Y5fZ5Y5kM3LN0Y5fZ5Y5kM3LN0Y5fZ5Y5kM3LN0Y5fZ5Y5kM3LN0Y5f"
|
||||
|
||||
# SSH
|
||||
ssh:
|
||||
install-server: true
|
||||
allow-pw: true
|
||||
|
||||
# Additional packages
|
||||
packages:
|
||||
- sway
|
||||
- seatd
|
||||
- pipewire
|
||||
- pipewire-pulse
|
||||
- foot
|
||||
- curl
|
||||
- git
|
||||
- htop
|
||||
|
||||
# Late commands - run after installation
|
||||
late-commands:
|
||||
# Add ecouser to required groups
|
||||
- curtin in-target -- usermod -aG sudo,video,render,input,seat ecouser
|
||||
|
||||
# Enable passwordless sudo for ecouser
|
||||
- echo "ecouser ALL=(ALL) NOPASSWD:ALL" > /target/etc/sudoers.d/ecouser
|
||||
- chmod 440 /target/etc/sudoers.d/ecouser
|
||||
|
||||
# Enable seatd
|
||||
- curtin in-target -- systemctl enable seatd
|
||||
|
||||
# Install Deno
|
||||
- curtin in-target -- curl -fsSL https://deno.land/install.sh | DENO_INSTALL=/opt/eco sh
|
||||
|
||||
# Copy eco-daemon files (assumes they're on the ISO)
|
||||
- cp -r /cdrom/eco/* /target/opt/eco/
|
||||
|
||||
# Enable eco-daemon service
|
||||
- curtin in-target -- systemctl enable eco-daemon
|
||||
|
||||
# Install Google Chrome
|
||||
- curtin in-target -- wget -q -O /tmp/chrome.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
|
||||
- curtin in-target -- apt-get install -y /tmp/chrome.deb
|
||||
- curtin in-target -- rm /tmp/chrome.deb
|
||||
|
||||
# Reboot after installation
|
||||
shutdown: reboot
|
||||
27
isobuild/config/hooks/normal/0050-setup-ecouser.hook.chroot
Executable file
27
isobuild/config/hooks/normal/0050-setup-ecouser.hook.chroot
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
# Create ecouser for running Sway and Chrome
|
||||
|
||||
set -e
|
||||
|
||||
echo "Creating ecouser..."
|
||||
|
||||
# Create ecouser with home directory and GECOS field (prevents "I have no name!" in terminal)
|
||||
useradd -m -s /bin/bash -c "EcoOS User" ecouser || true
|
||||
|
||||
# Add ecouser to necessary groups:
|
||||
# video,render - GPU access
|
||||
# audio - audio access
|
||||
# input - input devices
|
||||
# seat - seatd compositor access
|
||||
# sudo - sudo privileges
|
||||
# adm,cdrom,plugdev - standard Ubuntu groups
|
||||
usermod -aG video,render,audio,input,seat,sudo,adm,cdrom,plugdev ecouser || true
|
||||
|
||||
# Set a default password (ecouser:ecouser)
|
||||
echo "ecouser:ecouser" | chpasswd
|
||||
|
||||
# Enable sudo without password for ecouser
|
||||
echo "ecouser ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/ecouser
|
||||
chmod 440 /etc/sudoers.d/ecouser
|
||||
|
||||
echo "ecouser created."
|
||||
14
isobuild/config/hooks/normal/0055-fix-networkmanager.hook.chroot
Executable file
14
isobuild/config/hooks/normal/0055-fix-networkmanager.hook.chroot
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
# Fix NetworkManager connection file permissions
|
||||
|
||||
set -e
|
||||
|
||||
echo "Fixing NetworkManager connection permissions..."
|
||||
|
||||
# NetworkManager requires connection files to be owned by root:root with 600 permissions
|
||||
if [ -d /etc/NetworkManager/system-connections ]; then
|
||||
chown -R root:root /etc/NetworkManager/system-connections
|
||||
chmod 600 /etc/NetworkManager/system-connections/*.nmconnection 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo "NetworkManager permissions fixed."
|
||||
19
isobuild/config/hooks/normal/0060-install-chrome.hook.chroot
Executable file
19
isobuild/config/hooks/normal/0060-install-chrome.hook.chroot
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/bin/sh
|
||||
# Install Chromium browser (from Ubuntu repos - more reliable than downloading Chrome)
|
||||
|
||||
set -e
|
||||
|
||||
echo "Installing Chromium browser..."
|
||||
|
||||
# Install Chromium from Ubuntu repos
|
||||
apt-get update
|
||||
apt-get install -y chromium-browser
|
||||
|
||||
# Create symlink so scripts expecting google-chrome-stable work
|
||||
ln -sf /usr/bin/chromium-browser /usr/bin/google-chrome-stable
|
||||
|
||||
# Clean up
|
||||
apt-get clean
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
echo "Chromium browser installed."
|
||||
29
isobuild/config/hooks/normal/0100-enable-services.hook.chroot
Executable file
29
isobuild/config/hooks/normal/0100-enable-services.hook.chroot
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh
|
||||
# Enable EcoOS services
|
||||
|
||||
set -e
|
||||
|
||||
echo "Enabling systemd-networkd for static IP..."
|
||||
systemctl enable systemd-networkd.service
|
||||
systemctl enable systemd-networkd-wait-online.service
|
||||
|
||||
echo "Disabling NetworkManager (using networkd instead)..."
|
||||
systemctl disable NetworkManager.service 2>/dev/null || true
|
||||
systemctl mask NetworkManager.service 2>/dev/null || true
|
||||
|
||||
echo "Enabling seatd service..."
|
||||
systemctl enable seatd.service
|
||||
|
||||
echo "Enabling eco-daemon service..."
|
||||
systemctl enable eco-daemon.service
|
||||
|
||||
echo "Enabling installer service..."
|
||||
systemctl enable ecoos-installer.service
|
||||
|
||||
echo "Enabling SSH service..."
|
||||
systemctl enable ssh.service || true
|
||||
|
||||
echo "Enabling debug service..."
|
||||
systemctl enable debug-network.service || true
|
||||
|
||||
echo "Services enabled."
|
||||
52
isobuild/config/hooks/normal/0200-fix-permissions.hook.chroot
Executable file
52
isobuild/config/hooks/normal/0200-fix-permissions.hook.chroot
Executable file
@@ -0,0 +1,52 @@
|
||||
#!/bin/sh
|
||||
# Final permissions fix before squashfs creation
|
||||
# Ensures /etc and critical directories have correct permissions
|
||||
# This is CRITICAL - wrong permissions break login, networking, and services
|
||||
|
||||
set -e
|
||||
|
||||
echo "Fixing critical directory permissions..."
|
||||
|
||||
# /etc must be world-readable for systemd and other services to work
|
||||
chmod 755 /etc
|
||||
|
||||
# Fix all subdirectories in /etc that need to be readable
|
||||
for dir in /etc/systemd /etc/systemd/system /etc/systemd/network \
|
||||
/etc/default /etc/security /etc/pam.d /etc/skel \
|
||||
/etc/profile.d /etc/sudoers.d /etc/bash_completion.d \
|
||||
/etc/apt /etc/dpkg /etc/ssl /etc/ssh /etc/sway; do
|
||||
if [ -d "$dir" ]; then
|
||||
chmod 755 "$dir"
|
||||
fi
|
||||
done
|
||||
|
||||
# Critical files that must be world-readable for system to function
|
||||
# These are essential for user/group lookups and shell login
|
||||
for file in /etc/passwd /etc/group /etc/hosts /etc/hostname \
|
||||
/etc/profile /etc/bash.bashrc /etc/environment \
|
||||
/etc/shells /etc/nsswitch.conf /etc/resolv.conf \
|
||||
/etc/machine-id /etc/ld.so.conf; do
|
||||
if [ -f "$file" ]; then
|
||||
chmod 644 "$file"
|
||||
fi
|
||||
done
|
||||
|
||||
# Shadow files should be root-only readable
|
||||
chmod 640 /etc/shadow 2>/dev/null || true
|
||||
chmod 640 /etc/gshadow 2>/dev/null || true
|
||||
|
||||
# Sudoers files need specific permissions
|
||||
chmod 440 /etc/sudoers 2>/dev/null || true
|
||||
if [ -d /etc/sudoers.d ]; then
|
||||
find /etc/sudoers.d -type f -exec chmod 440 {} \;
|
||||
fi
|
||||
|
||||
# Fix network config file permissions
|
||||
if [ -f /etc/systemd/network/10-wired.network ]; then
|
||||
chmod 644 /etc/systemd/network/10-wired.network
|
||||
fi
|
||||
|
||||
# Recursively fix /etc - directories should be 755, files 644 (except special cases)
|
||||
find /etc -type d -exec chmod 755 {} \; 2>/dev/null || true
|
||||
|
||||
echo "Permissions fixed."
|
||||
@@ -0,0 +1,17 @@
|
||||
[connection]
|
||||
id=Wired connection
|
||||
uuid=2b8f4e84-9c7d-4b3e-8f2a-1d5e6f7a8b9c
|
||||
type=ethernet
|
||||
autoconnect=true
|
||||
autoconnect-priority=100
|
||||
|
||||
[ethernet]
|
||||
|
||||
[ipv4]
|
||||
method=manual
|
||||
addresses=10.0.2.15/24
|
||||
gateway=10.0.2.2
|
||||
dns=10.0.2.3;
|
||||
|
||||
[ipv6]
|
||||
method=ignore
|
||||
@@ -0,0 +1,5 @@
|
||||
[Match]
|
||||
Name=ens* enp* eth*
|
||||
|
||||
[Network]
|
||||
DHCP=yes
|
||||
@@ -0,0 +1,12 @@
|
||||
[Unit]
|
||||
Description=Debug Network Info to Serial
|
||||
After=network-online.target eco-daemon.service
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=/bin/bash -c 'echo "=== NETWORK DEBUG ===" > /dev/ttyS0; ip addr >> /dev/ttyS0; echo "=== ROUTES ===" >> /dev/ttyS0; ip route >> /dev/ttyS0; echo "=== LISTENING ===" >> /dev/ttyS0; ss -tlnp >> /dev/ttyS0; echo "=== NM STATUS ===" >> /dev/ttyS0; nmcli device status >> /dev/ttyS0 2>&1; nmcli connection show >> /dev/ttyS0 2>&1; echo "=== END DEBUG ===" >> /dev/ttyS0'
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,24 @@
|
||||
[Unit]
|
||||
Description=EcoOS Daemon
|
||||
After=network-online.target seatd.service systemd-networkd-wait-online.service
|
||||
Wants=seatd.service network-online.target
|
||||
Requires=seatd.service
|
||||
StartLimitIntervalSec=0
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/opt/eco/bin/eco-daemon
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
WorkingDirectory=/opt/eco
|
||||
|
||||
# Give daemon enough capabilities
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_SYS_ADMIN
|
||||
|
||||
# Logging
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=eco-daemon
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,18 @@
|
||||
[Unit]
|
||||
Description=EcoOS Installer
|
||||
After=multi-user.target getty@tty1.service
|
||||
ConditionKernelCommandLine=ecoos_install=1
|
||||
Conflicts=getty@tty1.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/opt/eco/installer/install.sh
|
||||
StandardInput=tty
|
||||
StandardOutput=tty
|
||||
StandardError=tty
|
||||
TTYPath=/dev/tty1
|
||||
TTYReset=yes
|
||||
TTYVHangup=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1 @@
|
||||
/etc/systemd/system/ecoos-installer.service
|
||||
BIN
isobuild/config/includes.chroot/opt/eco/bin/eco-daemon
Executable file
BIN
isobuild/config/includes.chroot/opt/eco/bin/eco-daemon
Executable file
Binary file not shown.
22
isobuild/config/includes.chroot/opt/eco/debug-network.sh
Executable file
22
isobuild/config/includes.chroot/opt/eco/debug-network.sh
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
# Debug network info - outputs to serial console
|
||||
exec > /dev/ttyS0 2>&1
|
||||
|
||||
echo "=== NETWORK DEBUG ==="
|
||||
echo "Date: $(date)"
|
||||
echo ""
|
||||
echo "=== IP ADDRESSES ==="
|
||||
ip addr
|
||||
echo ""
|
||||
echo "=== ROUTES ==="
|
||||
ip route
|
||||
echo ""
|
||||
echo "=== NETWORKMANAGER CONNECTIONS ==="
|
||||
nmcli connection show
|
||||
echo ""
|
||||
echo "=== NETWORKMANAGER DEVICES ==="
|
||||
nmcli device status
|
||||
echo ""
|
||||
echo "=== LISTENING PORTS ==="
|
||||
ss -tlnp
|
||||
echo "=== END DEBUG ==="
|
||||
545
isobuild/config/includes.chroot/opt/eco/installer/install.sh
Executable file
545
isobuild/config/includes.chroot/opt/eco/installer/install.sh
Executable file
@@ -0,0 +1,545 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# EcoOS Installer
|
||||
# Installs EcoOS from live USB to disk
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
TIMEOUT=10
|
||||
HOSTNAME="ecoos"
|
||||
USERNAME="ecouser"
|
||||
SQUASHFS_PATH="/run/live/medium/live/filesystem.squashfs"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
log() {
|
||||
echo -e "${GREEN}[EcoOS]${NC} $1" >&2
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1" >&2
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Get the device the live system is running from
|
||||
get_live_device() {
|
||||
local live_dev=""
|
||||
# Find the device backing /run/live/medium
|
||||
if mountpoint -q /run/live/medium 2>/dev/null; then
|
||||
live_dev=$(findmnt -n -o SOURCE /run/live/medium | sed 's/[0-9]*$//')
|
||||
fi
|
||||
# Also check /cdrom for older casper
|
||||
if [ -z "$live_dev" ] && mountpoint -q /cdrom 2>/dev/null; then
|
||||
live_dev=$(findmnt -n -o SOURCE /cdrom | sed 's/[0-9]*$//')
|
||||
fi
|
||||
echo "$live_dev"
|
||||
}
|
||||
|
||||
# List available disks (excluding live media and loop devices)
|
||||
list_disks() {
|
||||
local live_dev=$(get_live_device)
|
||||
local disks=()
|
||||
|
||||
for disk in /sys/block/sd* /sys/block/nvme* /sys/block/vd*; do
|
||||
[ -e "$disk" ] || continue
|
||||
local name=$(basename "$disk")
|
||||
local dev="/dev/$name"
|
||||
|
||||
# Skip if this is the live media
|
||||
if [ "$dev" = "$live_dev" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Skip removable devices (USB sticks) - but allow if it's the only option
|
||||
local removable=$(cat "$disk/removable" 2>/dev/null || echo "0")
|
||||
|
||||
# Get size in GB
|
||||
local size_bytes=$(cat "$disk/size" 2>/dev/null || echo "0")
|
||||
local size_gb=$((size_bytes * 512 / 1024 / 1024 / 1024))
|
||||
|
||||
# Skip disks smaller than 10GB
|
||||
if [ "$size_gb" -lt 10 ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Get model
|
||||
local model=$(cat "$disk/device/model" 2>/dev/null | tr -d '\n' || echo "Unknown")
|
||||
|
||||
disks+=("$dev|$size_gb|$model|$removable")
|
||||
done
|
||||
|
||||
printf '%s\n' "${disks[@]}"
|
||||
}
|
||||
|
||||
# Select disk with timeout
|
||||
# All UI output goes to stderr so stdout only returns the device path
|
||||
select_disk() {
|
||||
local disks
|
||||
mapfile -t disks < <(list_disks)
|
||||
|
||||
if [ ${#disks[@]} -eq 0 ]; then
|
||||
error "No suitable disks found for installation"
|
||||
fi
|
||||
|
||||
echo "" >&2
|
||||
echo -e "${BLUE}╔════════════════════════════════════════════════════════════╗${NC}" >&2
|
||||
echo -e "${BLUE}║${NC} ${GREEN}EcoOS Disk Installation${NC} ${BLUE}║${NC}" >&2
|
||||
echo -e "${BLUE}╚════════════════════════════════════════════════════════════╝${NC}" >&2
|
||||
echo "" >&2
|
||||
echo "Available disks:" >&2
|
||||
echo "" >&2
|
||||
|
||||
local i=1
|
||||
local default_disk=""
|
||||
local default_idx=1
|
||||
local max_size=0
|
||||
|
||||
for disk_info in "${disks[@]}"; do
|
||||
IFS='|' read -r dev size model removable <<< "$disk_info"
|
||||
|
||||
local marker=""
|
||||
if [ "$size" -gt "$max_size" ]; then
|
||||
max_size=$size
|
||||
default_disk=$dev
|
||||
default_idx=$i
|
||||
fi
|
||||
|
||||
printf " ${YELLOW}%d)${NC} %-12s %4d GB %s\n" "$i" "$dev" "$size" "$model" >&2
|
||||
((i++))
|
||||
done
|
||||
|
||||
echo "" >&2
|
||||
echo -e "Default: ${GREEN}$default_disk${NC} (largest disk)" >&2
|
||||
echo "" >&2
|
||||
echo -e "${YELLOW}WARNING: Selected disk will be COMPLETELY ERASED!${NC}" >&2
|
||||
echo "" >&2
|
||||
|
||||
# Countdown with input check
|
||||
local selected=""
|
||||
local remaining=$TIMEOUT
|
||||
|
||||
while [ $remaining -gt 0 ]; do
|
||||
printf "\rSelect disk [1-%d] or press Enter for default (%ds remaining): " "${#disks[@]}" "$remaining" >&2
|
||||
|
||||
if read -t 1 -n 1 input; then
|
||||
if [ -z "$input" ]; then
|
||||
# Enter pressed - use default
|
||||
selected=$default_idx
|
||||
break
|
||||
elif [[ "$input" =~ ^[0-9]$ ]] && [ "$input" -ge 1 ] && [ "$input" -le ${#disks[@]} ]; then
|
||||
selected=$input
|
||||
echo "" >&2
|
||||
break
|
||||
else
|
||||
echo "" >&2
|
||||
warn "Invalid selection. Please enter 1-${#disks[@]}"
|
||||
remaining=$TIMEOUT
|
||||
fi
|
||||
fi
|
||||
((remaining--))
|
||||
done
|
||||
|
||||
if [ -z "$selected" ]; then
|
||||
selected=$default_idx
|
||||
echo "" >&2
|
||||
log "Timeout - auto-selecting default disk"
|
||||
fi
|
||||
|
||||
# Get selected disk
|
||||
local idx=$((selected - 1))
|
||||
IFS='|' read -r TARGET_DISK size model removable <<< "${disks[$idx]}"
|
||||
|
||||
echo "" >&2
|
||||
log "Selected: $TARGET_DISK ($size GB - $model)"
|
||||
echo "" >&2
|
||||
|
||||
# Final confirmation with shorter timeout
|
||||
echo -e "${RED}╔════════════════════════════════════════════════════════════╗${NC}" >&2
|
||||
echo -e "${RED}║ ALL DATA ON $TARGET_DISK WILL BE PERMANENTLY DESTROYED! ║${NC}" >&2
|
||||
echo -e "${RED}╚════════════════════════════════════════════════════════════╝${NC}" >&2
|
||||
echo "" >&2
|
||||
|
||||
local confirm_timeout=10
|
||||
printf "Press 'y' to confirm, any other key to cancel (%ds): " "$confirm_timeout" >&2
|
||||
|
||||
if read -t $confirm_timeout -n 1 confirm; then
|
||||
echo "" >&2
|
||||
if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
|
||||
error "Installation cancelled by user"
|
||||
fi
|
||||
else
|
||||
echo "" >&2
|
||||
log "Auto-confirming installation..."
|
||||
fi
|
||||
|
||||
# Only this goes to stdout - the actual device path
|
||||
echo "$TARGET_DISK"
|
||||
}
|
||||
|
||||
# Partition the disk
|
||||
partition_disk() {
|
||||
local disk=$1
|
||||
|
||||
log "Partitioning $disk..."
|
||||
|
||||
# Wipe existing partition table
|
||||
wipefs -a "$disk" >/dev/null 2>&1 || true
|
||||
|
||||
# Create GPT partition table
|
||||
parted -s "$disk" mklabel gpt
|
||||
|
||||
# Create EFI partition (512MB)
|
||||
parted -s "$disk" mkpart ESP fat32 1MiB 513MiB
|
||||
parted -s "$disk" set 1 esp on
|
||||
|
||||
# Create root partition (rest of disk)
|
||||
parted -s "$disk" mkpart root ext4 513MiB 100%
|
||||
|
||||
# Wait for partitions to appear
|
||||
sleep 2
|
||||
partprobe "$disk"
|
||||
sleep 1
|
||||
|
||||
# Determine partition names (nvme vs sd)
|
||||
if [[ "$disk" == *"nvme"* ]]; then
|
||||
EFI_PART="${disk}p1"
|
||||
ROOT_PART="${disk}p2"
|
||||
else
|
||||
EFI_PART="${disk}1"
|
||||
ROOT_PART="${disk}2"
|
||||
fi
|
||||
|
||||
log "Created partitions: EFI=$EFI_PART, Root=$ROOT_PART"
|
||||
}
|
||||
|
||||
# Format partitions
|
||||
format_partitions() {
|
||||
log "Formatting partitions..."
|
||||
|
||||
mkfs.fat -F 32 -n "EFI" "$EFI_PART"
|
||||
mkfs.ext4 -F -L "EcoOS" "$ROOT_PART"
|
||||
|
||||
log "Partitions formatted"
|
||||
}
|
||||
|
||||
# Mount partitions
|
||||
mount_partitions() {
|
||||
log "Mounting partitions..."
|
||||
|
||||
mkdir -p /mnt/target
|
||||
mount "$ROOT_PART" /mnt/target
|
||||
|
||||
mkdir -p /mnt/target/boot/efi
|
||||
mount "$EFI_PART" /mnt/target/boot/efi
|
||||
|
||||
log "Partitions mounted at /mnt/target"
|
||||
}
|
||||
|
||||
# Copy system files
|
||||
copy_system() {
|
||||
log "Copying system files (this may take several minutes)..."
|
||||
|
||||
# Check if squashfs exists
|
||||
if [ ! -f "$SQUASHFS_PATH" ]; then
|
||||
# Try alternative paths (including casper paths for Ubuntu)
|
||||
for path in /run/live/medium/live/filesystem.squashfs \
|
||||
/cdrom/casper/filesystem.squashfs \
|
||||
/cdrom/live/filesystem.squashfs \
|
||||
/isodevice/casper/filesystem.squashfs \
|
||||
/lib/live/mount/medium/live/filesystem.squashfs \
|
||||
/rofs/../casper/filesystem.squashfs; do
|
||||
if [ -f "$path" ]; then
|
||||
SQUASHFS_PATH="$path"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# If still not found, try to find it
|
||||
if [ ! -f "$SQUASHFS_PATH" ]; then
|
||||
local found=$(find /cdrom /run /media -name "filesystem.squashfs" 2>/dev/null | head -1)
|
||||
if [ -n "$found" ]; then
|
||||
SQUASHFS_PATH="$found"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -f "$SQUASHFS_PATH" ]; then
|
||||
error "Cannot find filesystem.squashfs"
|
||||
fi
|
||||
|
||||
log "Extracting from $SQUASHFS_PATH..."
|
||||
|
||||
# Extract squashfs
|
||||
unsquashfs -f -d /mnt/target "$SQUASHFS_PATH"
|
||||
|
||||
log "System files copied"
|
||||
}
|
||||
|
||||
# Configure the installed system
|
||||
configure_system() {
|
||||
log "Configuring system..."
|
||||
|
||||
# Get UUIDs
|
||||
local root_uuid=$(blkid -s UUID -o value "$ROOT_PART")
|
||||
local efi_uuid=$(blkid -s UUID -o value "$EFI_PART")
|
||||
|
||||
# Create fstab
|
||||
cat > /mnt/target/etc/fstab << EOF
|
||||
# EcoOS fstab
|
||||
UUID=$root_uuid / ext4 defaults,noatime 0 1
|
||||
UUID=$efi_uuid /boot/efi vfat umask=0077 0 1
|
||||
EOF
|
||||
|
||||
# Set hostname
|
||||
echo "$HOSTNAME" > /mnt/target/etc/hostname
|
||||
cat > /mnt/target/etc/hosts << EOF
|
||||
127.0.0.1 localhost
|
||||
127.0.1.1 $HOSTNAME
|
||||
|
||||
::1 localhost ip6-localhost ip6-loopback
|
||||
ff02::1 ip6-allnodes
|
||||
ff02::2 ip6-allrouters
|
||||
EOF
|
||||
|
||||
# Ensure seat group exists (for seatd)
|
||||
if ! grep -q "^seat:" /mnt/target/etc/group; then
|
||||
chroot /mnt/target groupadd seat 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Ensure render group exists (for GPU access)
|
||||
if ! grep -q "^render:" /mnt/target/etc/group; then
|
||||
chroot /mnt/target groupadd render 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Ensure ecouser exists and is configured
|
||||
# Groups: video,render (GPU), audio, input (devices), sudo, seat (seatd)
|
||||
if ! grep -q "^$USERNAME:" /mnt/target/etc/passwd; then
|
||||
chroot /mnt/target useradd -m -s /bin/bash -c "EcoOS User" -G video,render,audio,input,sudo,adm,cdrom,plugdev,seat "$USERNAME"
|
||||
else
|
||||
# Add to required groups if user already exists
|
||||
chroot /mnt/target usermod -a -G video,render,seat "$USERNAME" 2>/dev/null || true
|
||||
# Set the GECOS/full name field if missing
|
||||
chroot /mnt/target chfn -f "EcoOS User" "$USERNAME" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Create Sway config directory for ecouser
|
||||
mkdir -p /mnt/target/home/$USERNAME/.config/sway
|
||||
if [ -f /mnt/target/etc/sway/config ]; then
|
||||
cp /mnt/target/etc/sway/config /mnt/target/home/$USERNAME/.config/sway/config
|
||||
fi
|
||||
chroot /mnt/target chown -R $USERNAME:$USERNAME /home/$USERNAME/.config 2>/dev/null || true
|
||||
|
||||
# Set a default password (ecouser:ecouser) - should be changed on first boot
|
||||
echo "$USERNAME:ecouser" | chroot /mnt/target chpasswd
|
||||
|
||||
# Enable sudo for ecouser
|
||||
echo "$USERNAME ALL=(ALL) NOPASSWD:ALL" > /mnt/target/etc/sudoers.d/ecouser
|
||||
chmod 440 /mnt/target/etc/sudoers.d/ecouser
|
||||
|
||||
# Remove live-boot packages marker if present
|
||||
rm -f /mnt/target/etc/live 2>/dev/null || true
|
||||
|
||||
# Enable systemd-networkd for static IP (more reliable than NetworkManager)
|
||||
chroot /mnt/target systemctl enable systemd-networkd.service 2>/dev/null || true
|
||||
chroot /mnt/target systemctl enable systemd-networkd-wait-online.service 2>/dev/null || true
|
||||
|
||||
# Disable NetworkManager to avoid conflicts
|
||||
chroot /mnt/target systemctl disable NetworkManager.service 2>/dev/null || true
|
||||
chroot /mnt/target systemctl mask NetworkManager.service 2>/dev/null || true
|
||||
|
||||
# Create network config for systemd-networkd (DHCP for QEMU/VMs)
|
||||
mkdir -p /mnt/target/etc/systemd/network
|
||||
cat > /mnt/target/etc/systemd/network/10-wired.network << 'NETEOF'
|
||||
[Match]
|
||||
Name=ens* enp* eth*
|
||||
|
||||
[Network]
|
||||
DHCP=yes
|
||||
NETEOF
|
||||
|
||||
# Fix critical directory permissions - /etc must be world-readable
|
||||
# for systemd-networkd and other services to read their config files
|
||||
# This is CRITICAL - squashfs may have wrong permissions from Docker build
|
||||
log "Fixing /etc permissions..."
|
||||
|
||||
# Fix /etc and all subdirectories recursively
|
||||
find /mnt/target/etc -type d -exec chmod 755 {} \;
|
||||
|
||||
# Fix critical files that must be world-readable
|
||||
for file in passwd group hosts hostname profile bash.bashrc environment \
|
||||
shells nsswitch.conf resolv.conf machine-id ld.so.conf; do
|
||||
if [ -f "/mnt/target/etc/$file" ]; then
|
||||
chmod 644 "/mnt/target/etc/$file"
|
||||
fi
|
||||
done
|
||||
|
||||
# Shadow files should be root-only readable
|
||||
chmod 640 /mnt/target/etc/shadow 2>/dev/null || true
|
||||
chmod 640 /mnt/target/etc/gshadow 2>/dev/null || true
|
||||
|
||||
# Sudoers files need specific permissions
|
||||
chmod 440 /mnt/target/etc/sudoers 2>/dev/null || true
|
||||
find /mnt/target/etc/sudoers.d -type f -exec chmod 440 {} \; 2>/dev/null || true
|
||||
|
||||
# Network config
|
||||
chmod 644 /mnt/target/etc/systemd/network/10-wired.network
|
||||
|
||||
log "systemd-networkd enabled for networking"
|
||||
|
||||
# Enable other services for installed system
|
||||
chroot /mnt/target systemctl enable eco-daemon.service 2>/dev/null || true
|
||||
chroot /mnt/target systemctl enable seatd.service 2>/dev/null || true
|
||||
chroot /mnt/target systemctl enable ssh.service 2>/dev/null || true
|
||||
chroot /mnt/target systemctl enable debug-network.service 2>/dev/null || true
|
||||
|
||||
log "System configured"
|
||||
}
|
||||
|
||||
# Install bootloader
|
||||
install_bootloader() {
|
||||
log "Installing GRUB bootloader..."
|
||||
|
||||
# Mount necessary filesystems for chroot
|
||||
mount --bind /dev /mnt/target/dev
|
||||
mount --bind /dev/pts /mnt/target/dev/pts
|
||||
mount --bind /proc /mnt/target/proc
|
||||
mount --bind /sys /mnt/target/sys
|
||||
mount --bind /run /mnt/target/run
|
||||
|
||||
# Fix GRUB default config - remove casper/live boot parameters and add serial console
|
||||
if [ -f /mnt/target/etc/default/grub ]; then
|
||||
# Remove any boot=casper or live-related parameters
|
||||
sed -i 's/boot=casper//g' /mnt/target/etc/default/grub
|
||||
# Update GRUB_CMDLINE_LINUX_DEFAULT with serial console for debugging
|
||||
sed -i 's/^GRUB_CMDLINE_LINUX_DEFAULT=.*/GRUB_CMDLINE_LINUX_DEFAULT="console=tty1 console=ttyS0,115200n8"/' /mnt/target/etc/default/grub
|
||||
# If line doesn't exist, add it
|
||||
if ! grep -q "GRUB_CMDLINE_LINUX_DEFAULT" /mnt/target/etc/default/grub; then
|
||||
echo 'GRUB_CMDLINE_LINUX_DEFAULT="console=tty1 console=ttyS0,115200n8"' >> /mnt/target/etc/default/grub
|
||||
fi
|
||||
# Enable serial terminal in GRUB
|
||||
echo 'GRUB_TERMINAL="console serial"' >> /mnt/target/etc/default/grub
|
||||
echo 'GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1"' >> /mnt/target/etc/default/grub
|
||||
fi
|
||||
|
||||
# Disable casper-related services
|
||||
log "Disabling live boot services..."
|
||||
chroot /mnt/target systemctl disable casper.service 2>/dev/null || true
|
||||
chroot /mnt/target systemctl disable casper-md5check.service 2>/dev/null || true
|
||||
chroot /mnt/target systemctl mask casper.service 2>/dev/null || true
|
||||
chroot /mnt/target systemctl mask casper-md5check.service 2>/dev/null || true
|
||||
|
||||
# Remove casper initramfs hooks to prevent live-boot behavior
|
||||
log "Removing casper initramfs hooks..."
|
||||
rm -rf /mnt/target/usr/share/initramfs-tools/scripts/casper 2>/dev/null || true
|
||||
rm -rf /mnt/target/usr/share/initramfs-tools/scripts/casper-premount 2>/dev/null || true
|
||||
rm -rf /mnt/target/usr/share/initramfs-tools/scripts/casper-bottom 2>/dev/null || true
|
||||
rm -f /mnt/target/usr/share/initramfs-tools/hooks/casper 2>/dev/null || true
|
||||
rm -f /mnt/target/etc/initramfs-tools/conf.d/casper.conf 2>/dev/null || true
|
||||
|
||||
# Regenerate initramfs without casper hooks
|
||||
log "Regenerating initramfs..."
|
||||
chroot /mnt/target update-initramfs -u -k all
|
||||
|
||||
# Ensure proper boot target
|
||||
chroot /mnt/target systemctl set-default multi-user.target 2>/dev/null || true
|
||||
|
||||
# Install GRUB
|
||||
chroot /mnt/target grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=EcoOS --recheck
|
||||
|
||||
# Generate GRUB config
|
||||
chroot /mnt/target update-grub
|
||||
|
||||
# Cleanup mounts (use lazy unmount for stubborn mounts, reverse order)
|
||||
sync
|
||||
umount -l /mnt/target/run 2>/dev/null || true
|
||||
umount -l /mnt/target/sys 2>/dev/null || true
|
||||
umount -l /mnt/target/proc 2>/dev/null || true
|
||||
umount -l /mnt/target/dev/pts 2>/dev/null || true
|
||||
umount -l /mnt/target/dev 2>/dev/null || true
|
||||
|
||||
log "Bootloader installed"
|
||||
}
|
||||
|
||||
# Cleanup and reboot
|
||||
cleanup_and_reboot() {
|
||||
log "Cleaning up..."
|
||||
|
||||
# Sync disks
|
||||
sync
|
||||
|
||||
# Unmount
|
||||
umount /mnt/target/boot/efi
|
||||
umount /mnt/target
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}╔════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${GREEN}║ EcoOS Installation Complete! ║${NC}"
|
||||
echo -e "${GREEN}╚════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
echo "The system will reboot in 10 seconds..."
|
||||
echo "Remove the USB drive when the screen goes blank."
|
||||
echo ""
|
||||
|
||||
sleep 10
|
||||
reboot
|
||||
}
|
||||
|
||||
# Main installation flow
|
||||
main() {
|
||||
clear
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}"
|
||||
echo " ███████╗ ██████╗ ██████╗ ██████╗ ███████╗"
|
||||
echo " ██╔════╝██╔════╝██╔═══██╗██╔═══██╗██╔════╝"
|
||||
echo " █████╗ ██║ ██║ ██║██║ ██║███████╗"
|
||||
echo " ██╔══╝ ██║ ██║ ██║██║ ██║╚════██║"
|
||||
echo " ███████╗╚██████╗╚██████╔╝╚██████╔╝███████║"
|
||||
echo " ╚══════╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝"
|
||||
echo -e "${NC}"
|
||||
echo " System Installer v1.0"
|
||||
echo ""
|
||||
|
||||
# Check if running as root
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
error "This script must be run as root"
|
||||
fi
|
||||
|
||||
# Select target disk
|
||||
TARGET_DISK=$(select_disk)
|
||||
|
||||
# Partition disk
|
||||
partition_disk "$TARGET_DISK"
|
||||
|
||||
# Format partitions
|
||||
format_partitions
|
||||
|
||||
# Mount partitions
|
||||
mount_partitions
|
||||
|
||||
# Copy system
|
||||
copy_system
|
||||
|
||||
# Configure system
|
||||
configure_system
|
||||
|
||||
# Install bootloader
|
||||
install_bootloader
|
||||
|
||||
# Cleanup and reboot
|
||||
cleanup_and_reboot
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
||||
21
isobuild/config/live-build/auto/config
Executable file
21
isobuild/config/live-build/auto/config
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
|
||||
# EcoOS live-build configuration
|
||||
# Note: EFI boot is handled manually in the Dockerfile build script
|
||||
|
||||
lb config noauto \
|
||||
--architectures amd64 \
|
||||
--distribution noble \
|
||||
--archive-areas "main restricted universe multiverse" \
|
||||
--binary-images iso-hybrid \
|
||||
--bootappend-live "boot=casper quiet splash" \
|
||||
--debian-installer false \
|
||||
--memtest none \
|
||||
--firmware-binary true \
|
||||
--firmware-chroot true \
|
||||
--updates true \
|
||||
--security true \
|
||||
--iso-application "EcoOS" \
|
||||
--iso-publisher "EcoBridge" \
|
||||
--iso-volume "EcoOS" \
|
||||
"${@}"
|
||||
57
isobuild/config/live-build/package-lists/base.list.chroot
Normal file
57
isobuild/config/live-build/package-lists/base.list.chroot
Normal file
@@ -0,0 +1,57 @@
|
||||
# EcoOS Base Packages
|
||||
# System essentials
|
||||
linux-image-generic
|
||||
linux-headers-generic
|
||||
systemd
|
||||
dbus
|
||||
network-manager
|
||||
openssh-server
|
||||
sudo
|
||||
|
||||
# EFI bootloader (required for UEFI boot)
|
||||
grub-efi-amd64
|
||||
grub-efi-amd64-signed
|
||||
shim-signed
|
||||
|
||||
# Sway + Wayland
|
||||
sway
|
||||
swaybg
|
||||
swaylock
|
||||
swayidle
|
||||
foot
|
||||
wl-clipboard
|
||||
xwayland
|
||||
|
||||
# Seat management
|
||||
seatd
|
||||
libseat1
|
||||
|
||||
# Tools
|
||||
curl
|
||||
wget
|
||||
git
|
||||
unzip
|
||||
htop
|
||||
vim
|
||||
nano
|
||||
tmux
|
||||
jq
|
||||
|
||||
# System utilities
|
||||
pciutils
|
||||
usbutils
|
||||
dmidecode
|
||||
lshw
|
||||
|
||||
# Installer requirements
|
||||
parted
|
||||
squashfs-tools
|
||||
dosfstools
|
||||
e2fsprogs
|
||||
|
||||
# Live-build binary phase requirements (pre-install to avoid DNS issues)
|
||||
mtools
|
||||
syslinux
|
||||
syslinux-common
|
||||
isolinux
|
||||
genisoimage
|
||||
32
isobuild/config/live-build/package-lists/desktop.list.chroot
Normal file
32
isobuild/config/live-build/package-lists/desktop.list.chroot
Normal file
@@ -0,0 +1,32 @@
|
||||
# EcoOS Desktop Packages
|
||||
|
||||
# Audio
|
||||
pipewire
|
||||
pipewire-pulse
|
||||
pipewire-alsa
|
||||
wireplumber
|
||||
libspa-0.2-bluetooth
|
||||
|
||||
# Fonts
|
||||
fonts-noto
|
||||
fonts-noto-color-emoji
|
||||
fonts-liberation
|
||||
fonts-dejavu
|
||||
|
||||
# Browser dependencies (Chrome installed via hook)
|
||||
libnss3
|
||||
libatk1.0-0
|
||||
libatk-bridge2.0-0
|
||||
libcups2
|
||||
libdrm2
|
||||
libxkbcommon0
|
||||
libxcomposite1
|
||||
libxdamage1
|
||||
libxfixes3
|
||||
libxrandr2
|
||||
libgbm1
|
||||
libasound2t64
|
||||
|
||||
# Utilities
|
||||
grim
|
||||
slurp
|
||||
39
isobuild/config/live-build/package-lists/drivers.list.chroot
Normal file
39
isobuild/config/live-build/package-lists/drivers.list.chroot
Normal file
@@ -0,0 +1,39 @@
|
||||
# EcoOS Driver Packages
|
||||
|
||||
# GPU drivers - Mesa (open source)
|
||||
xserver-xorg-video-all
|
||||
mesa-utils
|
||||
mesa-vulkan-drivers
|
||||
libgl1-mesa-dri
|
||||
libgbm1
|
||||
libegl1
|
||||
|
||||
# Intel GPU
|
||||
intel-media-va-driver
|
||||
libva-drm2
|
||||
libva2
|
||||
|
||||
# AMD GPU
|
||||
libdrm-amdgpu1
|
||||
|
||||
# All firmware (Ubuntu combines into linux-firmware)
|
||||
linux-firmware
|
||||
|
||||
# Storage
|
||||
nvme-cli
|
||||
smartmontools
|
||||
mdadm
|
||||
lvm2
|
||||
cryptsetup
|
||||
|
||||
# USB/Input
|
||||
libinput-tools
|
||||
libinput-bin
|
||||
|
||||
# Bluetooth
|
||||
bluez
|
||||
bluez-tools
|
||||
|
||||
# Virtualization support
|
||||
qemu-guest-agent
|
||||
open-vm-tools
|
||||
24
isobuild/config/systemd/eco-daemon.service
Normal file
24
isobuild/config/systemd/eco-daemon.service
Normal file
@@ -0,0 +1,24 @@
|
||||
[Unit]
|
||||
Description=EcoOS Daemon
|
||||
After=network-online.target seatd.service systemd-networkd-wait-online.service
|
||||
Wants=seatd.service network-online.target
|
||||
Requires=seatd.service
|
||||
StartLimitIntervalSec=0
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/opt/eco/bin/eco-daemon
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
WorkingDirectory=/opt/eco
|
||||
|
||||
# Give daemon enough capabilities
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_SYS_ADMIN
|
||||
|
||||
# Logging
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=eco-daemon
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
18
isobuild/deno.json
Normal file
18
isobuild/deno.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@ecobridge/eco-os-isobuild",
|
||||
"version": "0.0.1",
|
||||
"exports": "./mod.ts",
|
||||
"tasks": {
|
||||
"build": "deno run --allow-all mod.ts build",
|
||||
"clean": "deno run --allow-all mod.ts clean",
|
||||
"test-qemu": "deno run --allow-all mod.ts test-qemu"
|
||||
},
|
||||
"imports": {
|
||||
"@std/fs": "jsr:@std/fs@^1.0.0",
|
||||
"@std/path": "jsr:@std/path@^1.0.0",
|
||||
"@std/async": "jsr:@std/async@^1.0.0"
|
||||
},
|
||||
"compilerOptions": {
|
||||
"strict": true
|
||||
}
|
||||
}
|
||||
70
isobuild/mod.ts
Normal file
70
isobuild/mod.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* EcoOS ISO Builder
|
||||
*
|
||||
* CLI for building custom Ubuntu ISO with EcoOS daemon
|
||||
*/
|
||||
|
||||
import { build } from './ts/index.ts';
|
||||
|
||||
const command = Deno.args[0];
|
||||
|
||||
switch (command) {
|
||||
case 'build':
|
||||
await build();
|
||||
break;
|
||||
|
||||
case 'clean':
|
||||
console.log('Cleaning build artifacts...');
|
||||
try {
|
||||
await Deno.remove('./output', { recursive: true });
|
||||
await Deno.remove('./build', { recursive: true });
|
||||
console.log('Clean complete.');
|
||||
} catch {
|
||||
console.log('Nothing to clean.');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'test-qemu':
|
||||
console.log('Testing ISO in QEMU...');
|
||||
const isoPath = './output/ecoos.iso';
|
||||
try {
|
||||
await Deno.stat(isoPath);
|
||||
} catch {
|
||||
console.error('ISO not found. Run "deno task build" first.');
|
||||
Deno.exit(1);
|
||||
}
|
||||
|
||||
const qemu = new Deno.Command('qemu-system-x86_64', {
|
||||
args: [
|
||||
'-enable-kvm',
|
||||
'-m', '4G',
|
||||
'-cpu', 'host',
|
||||
'-smp', '2',
|
||||
'-cdrom', isoPath,
|
||||
'-boot', 'd',
|
||||
'-vga', 'virtio',
|
||||
'-display', 'gtk',
|
||||
'-device', 'usb-tablet',
|
||||
'-nic', 'user,hostfwd=tcp::3006-:3006',
|
||||
],
|
||||
stdout: 'inherit',
|
||||
stderr: 'inherit',
|
||||
});
|
||||
await qemu.spawn().status;
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log(`
|
||||
EcoOS ISO Builder
|
||||
|
||||
Usage:
|
||||
deno task build Build the ISO
|
||||
deno task clean Clean build artifacts
|
||||
deno task test-qemu Test ISO in QEMU
|
||||
|
||||
Requirements:
|
||||
- Ubuntu 24.04+ host
|
||||
- live-build, debootstrap, xorriso installed
|
||||
- sudo access for live-build
|
||||
`);
|
||||
}
|
||||
129
isobuild/scripts/build-iso.sh
Executable file
129
isobuild/scripts/build-iso.sh
Executable file
@@ -0,0 +1,129 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# EcoOS ISO Build Script
|
||||
# Wrapper script for building the EcoOS ISO
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
echo "=== EcoOS ISO Builder ==="
|
||||
echo ""
|
||||
|
||||
# Check if running as root (required for live-build)
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "This script requires root privileges for live-build."
|
||||
echo "Running with sudo..."
|
||||
exec sudo "$0" "$@"
|
||||
fi
|
||||
|
||||
# Check prerequisites
|
||||
echo "[1/6] Checking prerequisites..."
|
||||
|
||||
check_command() {
|
||||
if ! command -v "$1" &> /dev/null; then
|
||||
echo "Error: $1 is not installed"
|
||||
echo "Install with: apt install $2"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_command lb live-build
|
||||
check_command debootstrap debootstrap
|
||||
check_command xorriso xorriso
|
||||
check_command deno deno
|
||||
|
||||
echo " All prerequisites found."
|
||||
|
||||
# Check Ubuntu version
|
||||
. /etc/os-release
|
||||
if [[ "$VERSION_ID" != "24.04" && "$VERSION_ID" != "24.10" ]]; then
|
||||
echo "Warning: This script is designed for Ubuntu 24.04+"
|
||||
echo "Current version: $VERSION_ID"
|
||||
fi
|
||||
|
||||
# Bundle the daemon
|
||||
echo ""
|
||||
echo "[2/6] Bundling ecoos_daemon..."
|
||||
|
||||
DAEMON_DIR="$(dirname "$ROOT_DIR")/ecoos_daemon"
|
||||
cd "$DAEMON_DIR"
|
||||
deno compile --allow-all --output bundle/eco-daemon mod.ts
|
||||
echo " Daemon bundled."
|
||||
|
||||
# Prepare build directory
|
||||
echo ""
|
||||
echo "[3/6] Preparing build directory..."
|
||||
|
||||
BUILD_DIR="$ROOT_DIR/build"
|
||||
rm -rf "$BUILD_DIR"
|
||||
mkdir -p "$BUILD_DIR"
|
||||
cd "$BUILD_DIR"
|
||||
|
||||
# Configure live-build
|
||||
echo ""
|
||||
echo "[4/6] Configuring live-build..."
|
||||
|
||||
lb config \
|
||||
--architectures amd64 \
|
||||
--distribution noble \
|
||||
--archive-areas "main restricted universe multiverse" \
|
||||
--binary-images iso-hybrid \
|
||||
--debian-installer false \
|
||||
--memtest none \
|
||||
--firmware-binary true \
|
||||
--firmware-chroot true \
|
||||
--updates true \
|
||||
--security true \
|
||||
--bootloaders "grub-efi" \
|
||||
--uefi-secure-boot enable \
|
||||
--iso-application "EcoOS" \
|
||||
--iso-publisher "EcoBridge" \
|
||||
--iso-volume "EcoOS"
|
||||
|
||||
# Copy package lists
|
||||
echo ""
|
||||
echo "[5/6] Copying configuration files..."
|
||||
|
||||
cp "$ROOT_DIR/config/live-build/package-lists/"*.list.chroot config/package-lists/
|
||||
|
||||
# Prepare includes.chroot
|
||||
mkdir -p config/includes.chroot/opt/eco/bin
|
||||
mkdir -p config/includes.chroot/opt/eco/daemon
|
||||
mkdir -p config/includes.chroot/etc/systemd/system
|
||||
|
||||
# Copy daemon
|
||||
cp "$DAEMON_DIR/bundle/eco-daemon" config/includes.chroot/opt/eco/bin/
|
||||
chmod +x config/includes.chroot/opt/eco/bin/eco-daemon
|
||||
|
||||
# Copy daemon source as backup
|
||||
cp -r "$DAEMON_DIR/ts" config/includes.chroot/opt/eco/daemon/
|
||||
cp "$DAEMON_DIR/mod.ts" config/includes.chroot/opt/eco/daemon/
|
||||
cp "$DAEMON_DIR/deno.json" config/includes.chroot/opt/eco/daemon/
|
||||
|
||||
# Copy systemd service
|
||||
cp "$ROOT_DIR/config/systemd/eco-daemon.service" config/includes.chroot/etc/systemd/system/
|
||||
|
||||
# Copy autoinstall config to binary includes
|
||||
mkdir -p config/includes.binary/autoinstall
|
||||
cp "$ROOT_DIR/config/autoinstall/user-data" config/includes.binary/autoinstall/
|
||||
touch config/includes.binary/autoinstall/meta-data
|
||||
|
||||
echo " Configuration complete."
|
||||
|
||||
# Build ISO
|
||||
echo ""
|
||||
echo "[6/6] Building ISO (this may take 15-30 minutes)..."
|
||||
echo ""
|
||||
|
||||
lb build
|
||||
|
||||
# Move ISO to output
|
||||
mkdir -p "$ROOT_DIR/output"
|
||||
mv *.iso "$ROOT_DIR/output/ecoos.iso" 2>/dev/null || true
|
||||
|
||||
echo ""
|
||||
echo "=== Build Complete ==="
|
||||
echo "ISO: $ROOT_DIR/output/ecoos.iso"
|
||||
34
isobuild/scripts/docker-build.sh
Executable file
34
isobuild/scripts/docker-build.sh
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Build EcoOS ISO using Docker
|
||||
# This avoids needing to install live-build on the host
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ISOBUILD_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
ECO_OS_DIR="$(dirname "$ISOBUILD_DIR")"
|
||||
|
||||
echo "=== EcoOS ISO Builder (Docker) ==="
|
||||
echo ""
|
||||
|
||||
cd "$ECO_OS_DIR"
|
||||
|
||||
# Build the Docker image
|
||||
echo "[1/2] Building Docker image..."
|
||||
docker build -t ecoos-builder -f isobuild/Dockerfile .
|
||||
|
||||
# Run the build
|
||||
echo ""
|
||||
echo "[2/2] Building ISO (this may take 15-30 minutes)..."
|
||||
mkdir -p "$ISOBUILD_DIR/output"
|
||||
|
||||
docker run --rm \
|
||||
--privileged \
|
||||
-v "$ISOBUILD_DIR/output:/output" \
|
||||
ecoos-builder
|
||||
|
||||
echo ""
|
||||
echo "=== Build Complete ==="
|
||||
echo "ISO: $ISOBUILD_DIR/output/ecoos.iso"
|
||||
45
isobuild/scripts/test-qemu.sh
Executable file
45
isobuild/scripts/test-qemu.sh
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Test EcoOS ISO in QEMU
|
||||
#
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
ISO_PATH="$ROOT_DIR/output/ecoos.iso"
|
||||
|
||||
if [ ! -f "$ISO_PATH" ]; then
|
||||
echo "Error: ISO not found at $ISO_PATH"
|
||||
echo "Run build-iso.sh first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Testing EcoOS ISO in QEMU..."
|
||||
echo "ISO: $ISO_PATH"
|
||||
echo ""
|
||||
echo "Management UI will be available at: http://localhost:3006"
|
||||
echo ""
|
||||
|
||||
# Create a temporary disk for installation testing
|
||||
DISK_PATH="/tmp/ecoos-test.qcow2"
|
||||
if [ ! -f "$DISK_PATH" ]; then
|
||||
echo "Creating test disk..."
|
||||
qemu-img create -f qcow2 "$DISK_PATH" 20G
|
||||
fi
|
||||
|
||||
qemu-system-x86_64 \
|
||||
-enable-kvm \
|
||||
-m 4G \
|
||||
-cpu host \
|
||||
-smp 2 \
|
||||
-cdrom "$ISO_PATH" \
|
||||
-drive file="$DISK_PATH",format=qcow2,if=virtio \
|
||||
-boot d \
|
||||
-vga virtio \
|
||||
-display gtk \
|
||||
-device usb-tablet \
|
||||
-device virtio-net-pci,netdev=net0 \
|
||||
-netdev user,id=net0,hostfwd=tcp::3006-:3006,hostfwd=tcp::2222-:22 \
|
||||
-bios /usr/share/ovmf/OVMF.fd
|
||||
|
||||
echo ""
|
||||
echo "QEMU session ended."
|
||||
254
isobuild/ts/index.ts
Normal file
254
isobuild/ts/index.ts
Normal file
@@ -0,0 +1,254 @@
|
||||
/**
|
||||
* ISO Build Orchestration
|
||||
*
|
||||
* Builds the EcoOS custom Ubuntu ISO
|
||||
*/
|
||||
|
||||
import * as path from '@std/path';
|
||||
|
||||
const ROOT = path.dirname(path.dirname(path.fromFileUrl(import.meta.url)));
|
||||
const DAEMON_DIR = path.join(path.dirname(ROOT), 'ecoos_daemon');
|
||||
const BUILD_DIR = path.join(ROOT, 'build');
|
||||
const OUTPUT_DIR = path.join(ROOT, 'output');
|
||||
const CONFIG_DIR = path.join(ROOT, 'config');
|
||||
|
||||
export async function build(): Promise<void> {
|
||||
console.log('=== EcoOS ISO Builder ===\n');
|
||||
|
||||
// Step 1: Check prerequisites
|
||||
console.log('[1/7] Checking prerequisites...');
|
||||
await checkPrerequisites();
|
||||
|
||||
// Step 2: Bundle the daemon
|
||||
console.log('[2/7] Bundling ecoos_daemon...');
|
||||
await bundleDaemon();
|
||||
|
||||
// Step 3: Prepare build directory
|
||||
console.log('[3/7] Preparing build directory...');
|
||||
await prepareBuildDir();
|
||||
|
||||
// Step 4: Configure live-build
|
||||
console.log('[4/7] Configuring live-build...');
|
||||
await configureLiveBuild();
|
||||
|
||||
// Step 5: Copy package lists
|
||||
console.log('[5/7] Copying package lists...');
|
||||
await copyPackageLists();
|
||||
|
||||
// Step 6: Copy daemon and configs to chroot includes
|
||||
console.log('[6/7] Preparing chroot includes...');
|
||||
await prepareChrootIncludes();
|
||||
|
||||
// Step 7: Build ISO
|
||||
console.log('[7/7] Building ISO (this may take a while)...');
|
||||
await buildIso();
|
||||
|
||||
console.log('\n=== Build Complete ===');
|
||||
console.log(`ISO: ${OUTPUT_DIR}/ecoos.iso`);
|
||||
}
|
||||
|
||||
async function checkPrerequisites(): Promise<void> {
|
||||
const commands = ['lb', 'debootstrap', 'xorriso'];
|
||||
|
||||
for (const cmd of commands) {
|
||||
try {
|
||||
const result = await run('which', [cmd]);
|
||||
if (!result.success) {
|
||||
throw new Error(`${cmd} not found`);
|
||||
}
|
||||
} catch {
|
||||
console.error(`Missing prerequisite: ${cmd}`);
|
||||
console.error('Install with: sudo apt install live-build debootstrap xorriso');
|
||||
Deno.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(' All prerequisites found.');
|
||||
}
|
||||
|
||||
async function bundleDaemon(): Promise<void> {
|
||||
// Compile the daemon to a single executable
|
||||
const bundleDir = path.join(DAEMON_DIR, 'bundle');
|
||||
await Deno.mkdir(bundleDir, { recursive: true });
|
||||
|
||||
const result = await run('deno', [
|
||||
'compile',
|
||||
'--allow-all',
|
||||
'--output', path.join(bundleDir, 'eco-daemon'),
|
||||
path.join(DAEMON_DIR, 'mod.ts'),
|
||||
], { cwd: DAEMON_DIR });
|
||||
|
||||
if (!result.success) {
|
||||
console.error('Failed to bundle daemon');
|
||||
console.error(result.stderr);
|
||||
Deno.exit(1);
|
||||
}
|
||||
|
||||
console.log(' Daemon bundled successfully.');
|
||||
}
|
||||
|
||||
async function prepareBuildDir(): Promise<void> {
|
||||
// Clean and create build directory
|
||||
try {
|
||||
await Deno.remove(BUILD_DIR, { recursive: true });
|
||||
} catch {
|
||||
// Directory may not exist
|
||||
}
|
||||
|
||||
await Deno.mkdir(BUILD_DIR, { recursive: true });
|
||||
console.log(' Build directory prepared.');
|
||||
}
|
||||
|
||||
async function configureLiveBuild(): Promise<void> {
|
||||
// Initialize live-build config
|
||||
const result = await run('lb', ['config'], { cwd: BUILD_DIR });
|
||||
|
||||
if (!result.success) {
|
||||
console.error('Failed to initialize live-build');
|
||||
console.error(result.stderr);
|
||||
Deno.exit(1);
|
||||
}
|
||||
|
||||
// Copy our auto/config
|
||||
const autoDir = path.join(BUILD_DIR, 'auto');
|
||||
await Deno.mkdir(autoDir, { recursive: true });
|
||||
|
||||
const configSrc = path.join(CONFIG_DIR, 'live-build', 'auto', 'config');
|
||||
const configDst = path.join(autoDir, 'config');
|
||||
|
||||
await Deno.copyFile(configSrc, configDst);
|
||||
await Deno.chmod(configDst, 0o755);
|
||||
|
||||
// Re-run lb config with our settings
|
||||
const result2 = await run('lb', ['config'], { cwd: BUILD_DIR });
|
||||
if (!result2.success) {
|
||||
console.error('Failed to configure live-build');
|
||||
Deno.exit(1);
|
||||
}
|
||||
|
||||
console.log(' live-build configured.');
|
||||
}
|
||||
|
||||
async function copyPackageLists(): Promise<void> {
|
||||
const srcDir = path.join(CONFIG_DIR, 'live-build', 'package-lists');
|
||||
const dstDir = path.join(BUILD_DIR, 'config', 'package-lists');
|
||||
|
||||
await Deno.mkdir(dstDir, { recursive: true });
|
||||
|
||||
for await (const entry of Deno.readDir(srcDir)) {
|
||||
if (entry.isFile && entry.name.endsWith('.list.chroot')) {
|
||||
const src = path.join(srcDir, entry.name);
|
||||
const dst = path.join(dstDir, entry.name);
|
||||
await Deno.copyFile(src, dst);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(' Package lists copied.');
|
||||
}
|
||||
|
||||
async function prepareChrootIncludes(): Promise<void> {
|
||||
const includesDir = path.join(BUILD_DIR, 'config', 'includes.chroot');
|
||||
|
||||
// Create directory structure
|
||||
await Deno.mkdir(path.join(includesDir, 'opt', 'eco', 'bin'), { recursive: true });
|
||||
await Deno.mkdir(path.join(includesDir, 'opt', 'eco', 'daemon'), { recursive: true });
|
||||
await Deno.mkdir(path.join(includesDir, 'etc', 'systemd', 'system'), { recursive: true });
|
||||
|
||||
// Copy bundled daemon
|
||||
const daemonSrc = path.join(DAEMON_DIR, 'bundle', 'eco-daemon');
|
||||
const daemonDst = path.join(includesDir, 'opt', 'eco', 'bin', 'eco-daemon');
|
||||
|
||||
try {
|
||||
await Deno.copyFile(daemonSrc, daemonDst);
|
||||
await Deno.chmod(daemonDst, 0o755);
|
||||
} catch (e) {
|
||||
console.error('Failed to copy daemon bundle:', e);
|
||||
// Fall back to copying source files
|
||||
console.log(' Copying daemon source files instead...');
|
||||
await copyDir(path.join(DAEMON_DIR, 'ts'), path.join(includesDir, 'opt', 'eco', 'daemon'));
|
||||
await Deno.copyFile(
|
||||
path.join(DAEMON_DIR, 'mod.ts'),
|
||||
path.join(includesDir, 'opt', 'eco', 'daemon', 'mod.ts')
|
||||
);
|
||||
}
|
||||
|
||||
// Copy systemd service
|
||||
const serviceSrc = path.join(CONFIG_DIR, 'systemd', 'eco-daemon.service');
|
||||
const serviceDst = path.join(includesDir, 'etc', 'systemd', 'system', 'eco-daemon.service');
|
||||
await Deno.copyFile(serviceSrc, serviceDst);
|
||||
|
||||
// Copy autoinstall config
|
||||
const autoinstallDir = path.join(BUILD_DIR, 'config', 'includes.binary');
|
||||
await Deno.mkdir(autoinstallDir, { recursive: true });
|
||||
|
||||
const userDataSrc = path.join(CONFIG_DIR, 'autoinstall', 'user-data');
|
||||
const userDataDst = path.join(autoinstallDir, 'autoinstall', 'user-data');
|
||||
await Deno.mkdir(path.dirname(userDataDst), { recursive: true });
|
||||
await Deno.copyFile(userDataSrc, userDataDst);
|
||||
|
||||
// Create empty meta-data file (required for cloud-init)
|
||||
await Deno.writeTextFile(path.join(path.dirname(userDataDst), 'meta-data'), '');
|
||||
|
||||
console.log(' Chroot includes prepared.');
|
||||
}
|
||||
|
||||
async function buildIso(): Promise<void> {
|
||||
const result = await run('sudo', ['lb', 'build'], {
|
||||
cwd: BUILD_DIR,
|
||||
stdout: 'inherit',
|
||||
stderr: 'inherit',
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
console.error('ISO build failed');
|
||||
Deno.exit(1);
|
||||
}
|
||||
|
||||
// Move ISO to output directory
|
||||
await Deno.mkdir(OUTPUT_DIR, { recursive: true });
|
||||
|
||||
for await (const entry of Deno.readDir(BUILD_DIR)) {
|
||||
if (entry.isFile && entry.name.endsWith('.iso')) {
|
||||
const src = path.join(BUILD_DIR, entry.name);
|
||||
const dst = path.join(OUTPUT_DIR, 'ecoos.iso');
|
||||
await Deno.rename(src, dst);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function run(
|
||||
cmd: string,
|
||||
args: string[],
|
||||
options: { cwd?: string; stdout?: 'piped' | 'inherit'; stderr?: 'piped' | 'inherit' } = {}
|
||||
): Promise<{ success: boolean; stdout: string; stderr: string }> {
|
||||
const command = new Deno.Command(cmd, {
|
||||
args,
|
||||
cwd: options.cwd,
|
||||
stdout: options.stdout ?? 'piped',
|
||||
stderr: options.stderr ?? 'piped',
|
||||
});
|
||||
|
||||
const result = await command.output();
|
||||
|
||||
return {
|
||||
success: result.success,
|
||||
stdout: new TextDecoder().decode(result.stdout),
|
||||
stderr: new TextDecoder().decode(result.stderr),
|
||||
};
|
||||
}
|
||||
|
||||
async function copyDir(src: string, dst: string): Promise<void> {
|
||||
await Deno.mkdir(dst, { recursive: true });
|
||||
|
||||
for await (const entry of Deno.readDir(src)) {
|
||||
const srcPath = path.join(src, entry.name);
|
||||
const dstPath = path.join(dst, entry.name);
|
||||
|
||||
if (entry.isDirectory) {
|
||||
await copyDir(srcPath, dstPath);
|
||||
} else {
|
||||
await Deno.copyFile(srcPath, dstPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user