pull/149/merge
cha0sCat 1 week ago committed by GitHub
commit 25daf447b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 218
      .github/workflows/README.md
  2. 415
      .github/workflows/test.yml

@ -0,0 +1,218 @@
# GitHub Actions Workflow for Testing debi.sh
## Overview
This workflow tests the Debian Network Reinstall Script (`debi.sh`) using KVM virtualization in GitHub Actions. It validates that the script correctly prepares a system for Debian installation, performs the actual installation, and verifies the newly installed system.
## Test Matrix
The workflow uses a semantic matrix strategy to test ~15 different configurations across:
- **Debian Versions**: 10 (Buster), 11 (Bullseye), 12 (Bookworm)
- **Mirrors**: Default, USTC (China), Cloudflare
- **Network Interface Naming**: Standard (`ethx`) or predictable names
- **User Account**: `root` or `debian`
- **Network Console**: Always enabled for remote installation monitoring
**Key combinations tested:**
- Debian 10 with default mirror (baseline)
- Debian 11 with all mirrors and both naming schemes
- Debian 12 with all mirrors and various user configurations
**Base Image**: All tests use Debian 11 (Bullseye) cloud image as the starting point, regardless of target version.
## How It Works
### 1. Environment Setup
- Enables KVM support on GitHub Actions runner
- Caches APT packages for faster subsequent runs
- Installs QEMU and related tools (qemu-kvm, cloud-utils, sshpass, etc.)
### 2. Base Image Caching
- Downloads Debian 11 cloud image (only once, then cached)
- Reuses cached image across all matrix jobs
- Significantly speeds up workflow execution
### 3. VM Preparation
- Creates a copy-on-write disk image from the cached base image
- Generates cloud-init configuration for initial VM setup
- Configures root SSH access with password `rootpass123`
### 4. VM Execution
- Starts QEMU VM with KVM acceleration
- Waits for SSH to become available
- Uploads `debi.sh` script to the VM
### 5. Script Testing
- **Builds arguments dynamically** from matrix parameters:
- `--version` from `matrix.version`
- `--ustc` or `--cloudflare` from `matrix.mirror`
- `--ethx` from `matrix.ethx`
- `--network-console` (always enabled)
- `--user` and `--password` from `matrix.user`
- Executes `debi.sh` with built arguments
- Captures output for debugging
### 6. Installation Validation
The workflow validates that the script:
- Successfully downloads Debian installer components to `/boot/debian-*/`
- Creates the required `linux` and `initrd.gz` files
- Updates GRUB configuration with Debian Installer entry
- Sets GRUB default to boot into the installer
### 7. Full Installation Test
After validation, the workflow:
- Reboots the VM to start the Debian installer
- Waits for the installation to complete (up to 30 minutes)
- Polls for SSH availability with the **new password** (`newpass123`)
- Verifies the newly installed system
### 7. Success Criteria
A test passes if:
- ✓ Script executes without critical errors
- ✓ Installer files exist in `/boot/debian-*/`
- ✓ GRUB configuration contains "Debian Installer" entry
- ✓ VM successfully reboots into installer
- ✓ Installation completes automatically
- ✓ New system boots and is accessible via SSH with new credentials
## Running the Tests
### Automatic Execution
Tests run automatically on:
- Push to `master` or `main` branch
- Pull requests targeting `master` or `main`
### Manual Execution
You can manually trigger the workflow:
1. Go to Actions tab in GitHub
2. Select "Test Debian Installation Script"
3. Click "Run workflow"
## Test Artifacts
Each test run uploads:
- `debi-output.log` - Complete output from script execution
- `serial.log` - VM serial console log
Access artifacts from the workflow run summary page.
## Interpreting Results
### ✅ Success
- All validation checks pass
- New system is accessible with new credentials
- Green checkmark in GitHub Actions UI
### ❌ Failure
Common failure scenarios and debugging:
1. **SSH connection timeout (old credentials)**
- Check serial console log
- Verify cloud-init configuration
2. **Installer files not downloaded**
- Review debi-output.log
- Check network connectivity
- Verify mirror availability
3. **GRUB update failed**
- Check for GRUB installation issues
- Verify disk partitioning
4. **Installation timeout**
- Installation may take longer than expected
- Check serial console for installer progress
- Verify network connectivity to mirrors
5. **Cannot connect with new credentials**
- Installation may have failed
- Check serial console for installation errors
- Verify preseed configuration
## Password Strategy
The workflow uses two different passwords to verify installation success:
| Phase | User | Password | Purpose |
|-------|------|----------|---------|
| Initial VM (cloud-init) | root | `rootpass123` | Access the base system to run debi.sh |
| New System (installed) | root/debian | `newpass123` | Verify the new system was installed |
If SSH connects with `newpass123`, it proves the new Debian system was installed successfully.
## Limitations
This workflow tests the complete installation process:
- ✓ Script argument parsing
- ✓ Installer file downloads
- ✓ GRUB configuration updates
- ✓ Reboot into installer
- ✓ Unattended installation
- ✓ New system verification
Note: Full installation takes 10-30 minutes per test case.
## Performance Optimization
The workflow includes several optimizations:
1. **APT Package Caching**: Dependencies are cached across workflow runs
2. **Base Image Caching**: Debian 11 cloud image is downloaded once and reused
3. **Single Base Image**: All tests use Debian 11 as starting point (smaller cache footprint)
4. **Matrix Exclusions**: Intelligent filtering reduces redundant test combinations
## Adding New Test Cases
The workflow uses a semantic matrix. To modify test coverage:
**Add a new Debian version:**
```yaml
matrix:
version: [10, 11, 12, 13] # Add 13
```
**Test with a new mirror:**
```yaml
matrix:
mirror: ['default', 'ustc', 'cloudflare', 'tuna'] # Add tuna
```
**Modify exclusions:**
```yaml
exclude:
# Add exclusions to prevent unwanted combinations
- version: 13
mirror: 'ustc' # Skip USTC for Debian 13
```
The workflow automatically builds command-line arguments from matrix parameters.
## Troubleshooting
### KVM not available
If KVM is not available, the workflow will fail. GitHub Actions runners support KVM, but some self-hosted runners may not.
### Network timeouts
If downloads fail, consider:
- Adding retry logic
- Using alternative mirrors
- Checking GitHub Actions network restrictions
### VM boot failures
Check the serial console log artifact for kernel messages and boot errors.
### Installation hangs
If the installation takes too long:
- Check the serial console for progress
- Verify mirror connectivity
- Consider using a faster mirror (USTC, Cloudflare)
### Cache issues
To clear caches and force fresh downloads:
1. Go to Actions tab → Caches
2. Delete relevant cache entries
3. Re-run the workflow
Cache keys are based on workflow file hash, so modifying the workflow automatically invalidates caches.

@ -0,0 +1,415 @@
name: Test Debian Installation Script
on:
push:
branches: [ master, main ]
pull_request:
branches: [ master, main ]
workflow_dispatch:
jobs:
test-debi:
name: "Debian ${{ matrix.version }} | ${{ matrix.mirror }} | ethx:${{ matrix.ethx }} | user:${{ matrix.user }} | nc:${{ matrix.network_console }}"
runs-on: ubuntu-22.04
timeout-minutes: 45
permissions:
contents: read
strategy:
fail-fast: false
matrix:
# Target Debian version to install
version: [10, 11, 12]
# Mirror configuration
mirror: ['default', 'ustc', 'cloudflare']
# Network interface naming
ethx: [true, false]
# User to create
user: ['root', 'debian']
# Enable network console for remote installation
network_console: [true]
# Exclude combinations to keep matrix manageable (~12 tests)
exclude:
# Debian 10 - only test with default mirror and root user
- version: 10
mirror: 'ustc'
- version: 10
mirror: 'cloudflare'
- version: 10
user: 'debian'
- version: 10
ethx: false
# For Debian 11 & 12, test key combinations
# Skip debian user with ustc (redundant)
- version: 11
mirror: 'ustc'
user: 'debian'
- version: 12
mirror: 'ustc'
user: 'debian'
# Skip cloudflare without ethx (less common)
- mirror: 'cloudflare'
ethx: false
# Skip default mirror without ethx for Debian 12
- version: 12
mirror: 'default'
ethx: false
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Cache APT packages
uses: actions/cache@v4
id: cache-apt
with:
path: ~/apt-cache
key: ${{ runner.os }}-apt-${{ hashFiles('.github/workflows/test.yml') }}
restore-keys: |
${{ runner.os }}-apt-
- name: Restore APT cache
if: steps.cache-apt.outputs.cache-hit == 'true'
run: |
if [ -d ~/apt-cache ] && [ "$(ls -A ~/apt-cache 2>/dev/null)" ]; then
sudo cp ~/apt-cache/*.deb /var/cache/apt/archives/ 2>/dev/null || true
fi
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y qemu-kvm qemu-utils cloud-image-utils genisoimage expect sshpass
- name: Save APT cache
if: steps.cache-apt.outputs.cache-hit != 'true'
run: |
mkdir -p ~/apt-cache
sudo cp /var/cache/apt/archives/*.deb ~/apt-cache/ 2>/dev/null || true
sudo chown -R $USER:$USER ~/apt-cache
- name: Check KVM availability
run: |
ls -la /dev/kvm
kvm-ok || true
- name: Cache Debian cloud image
id: cache-debian-image
uses: actions/cache@v4
with:
path: /tmp/debian-11-base.qcow2
key: debian-11-cloud-image-${{ hashFiles('.github/workflows/test.yml') }}
restore-keys: |
debian-11-cloud-image-
- name: Download Debian 11 cloud image
if: steps.cache-debian-image.outputs.cache-hit != 'true'
run: |
wget -O /tmp/debian-11-base.qcow2 "https://cloud.debian.org/images/cloud/bullseye/latest/debian-11-generic-amd64.qcow2"
- name: Create working disk image
run: |
qemu-img create -f qcow2 -F qcow2 -b /tmp/debian-11-base.qcow2 /tmp/test-disk.qcow2 20G
qemu-img resize /tmp/test-disk.qcow2 20G
- name: Create cloud-init configuration
run: |
mkdir -p /tmp/cloud-init
# meta-data
cat > /tmp/cloud-init/meta-data << 'EOF'
instance-id: test-debi-vm
local-hostname: test-debi
EOF
# user-data with root access
cat > /tmp/cloud-init/user-data << 'EOF'
#cloud-config
users:
- name: root
lock_passwd: false
plain_text_passwd: 'rootpass123'
ssh_pwauth: true
disable_root: false
packages:
- wget
- curl
runcmd:
- sed -i 's/^#PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config
- systemctl restart sshd
EOF
# Create ISO for cloud-init
genisoimage -output /tmp/cloud-init.iso \
-volid cidata -rational-rock -joliet \
/tmp/cloud-init/user-data /tmp/cloud-init/meta-data
- name: Start VM and wait for boot
run: |
# Start QEMU in background (daemonize mode doesn't support -nographic)
sudo qemu-system-x86_64 \
-machine type=pc,accel=kvm \
-cpu host \
-m 2048 \
-display none \
-drive file=/tmp/test-disk.qcow2,format=qcow2,if=virtio \
-cdrom /tmp/cloud-init.iso \
-net nic,model=virtio \
-net user,hostfwd=tcp::2222-:22 \
-serial file:/tmp/serial.log \
-monitor unix:/tmp/qemu-monitor.sock,server,nowait \
-daemonize
# Fix permissions on serial.log so it can be uploaded as artifact
sudo chmod 644 /tmp/serial.log
echo "Waiting for VM to boot and SSH to become available..."
for i in {1..60}; do
if sshpass -p 'rootpass123' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 root@localhost 'echo SSH ready' 2>/dev/null; then
echo "SSH is ready!"
break
fi
echo "Attempt $i/60: Waiting for SSH..."
sleep 5
done
# Verify SSH is working
sshpass -p 'rootpass123' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 root@localhost 'uname -a'
- name: Upload debi.sh to VM
run: |
sshpass -p 'rootpass123' scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -P 2222 \
./debi.sh root@localhost:/root/debi.sh
- name: Make script executable
run: |
sshpass -p 'rootpass123' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 root@localhost \
'chmod +x /root/debi.sh'
- name: Build debi.sh arguments from matrix parameters
id: build-args
run: |
# Start with version
ARGS="--version ${{ matrix.version }}"
# Add mirror flag
if [ "${{ matrix.mirror }}" = "ustc" ]; then
ARGS="$ARGS --ustc"
elif [ "${{ matrix.mirror }}" = "cloudflare" ]; then
ARGS="$ARGS --cloudflare"
fi
# default mirror doesn't need a flag
# Add ethx flag
if [ "${{ matrix.ethx }}" = "true" ]; then
ARGS="$ARGS --ethx"
fi
# Add network-console flag
if [ "${{ matrix.network_console }}" = "true" ]; then
ARGS="$ARGS --network-console"
fi
# Add user and password
ARGS="$ARGS --user ${{ matrix.user }} --password newpass123"
echo "args=$ARGS" >> $GITHUB_OUTPUT
echo "Generated arguments: $ARGS"
- name: Run debi.sh with test arguments
run: |
echo "Running debi.sh with arguments: ${{ steps.build-args.outputs.args }}"
# Run the script with a pseudo-TTY to handle stty commands
# Use 'script' command to provide a PTY for non-interactive SSH
sshpass -p 'rootpass123' ssh -t -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 root@localhost \
"script -q -c 'cd /root && ./debi.sh ${{ steps.build-args.outputs.args }}' /tmp/debi-output.log; echo \"Script exit code: \$?\" >> /tmp/debi-output.log" || echo "Script execution finished with non-zero exit code, will validate via file checks"
- name: Download and check debi.sh output
run: |
sshpass -p 'rootpass123' scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -P 2222 \
root@localhost:/tmp/debi-output.log /tmp/debi-output.log || echo "Could not download output log"
if [ -f /tmp/debi-output.log ]; then
echo "=== debi.sh output ==="
cat /tmp/debi-output.log
echo "======================"
fi
- name: Verify installation preparation
run: |
echo "Checking if Debian installer files were downloaded..."
# Check if installer directory was created
sshpass -p 'rootpass123' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 root@localhost \
'ls -la /boot/debian-* || echo "No installer directory found"'
# Check for installer components
sshpass -p 'rootpass123' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 root@localhost \
'ls -lh /boot/debian-*/linux /boot/debian-*/initrd.gz 2>/dev/null || echo "Installer files not found"'
# Check GRUB configuration
sshpass -p 'rootpass123' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 root@localhost \
'cat /etc/default/grub.d/zz-debi.cfg 2>/dev/null || echo "GRUB config not found"'
# Check if GRUB was updated with installer entry
sshpass -p 'rootpass123' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 root@localhost \
'grep -A 5 "Debian Installer" /boot/grub/grub.cfg || echo "Debian Installer entry not found in GRUB"'
- name: Validate installation files exist
run: |
# Verify key files exist
sshpass -p 'rootpass123' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 root@localhost \
'test -f /boot/debian-*/linux && test -f /boot/debian-*/initrd.gz && echo "Installation files verified!" || exit 1'
# Verify GRUB config was updated
sshpass -p 'rootpass123' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 root@localhost \
'grep -q "Debian Installer" /boot/grub/grub.cfg && echo "GRUB config verified!" || exit 1'
echo "✓ Script execution completed successfully"
echo "✓ Installer files downloaded"
echo "✓ GRUB configuration updated"
echo "✓ System ready for reboot into Debian installer"
- name: Reboot into Debian installer
run: |
echo "Rebooting VM to start Debian installation..."
# Trigger reboot - the system should boot into Debian installer
sshpass -p 'rootpass123' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 root@localhost \
'reboot' 2>/dev/null || true
echo "Reboot command sent. VM will now boot into Debian installer."
echo "The installation process will take several minutes..."
- name: Wait for Debian installation to complete
run: |
echo "Waiting for Debian installation to complete..."
echo "This typically takes 10-20 minutes depending on network speed and mirror."
echo "The installer will automatically reboot after completion."
# Wait for the old system to go down
sleep 30
# The installation process:
# 1. VM boots into Debian installer (netboot)
# 2. Installer runs unattended using preseed configuration
# 3. System installs packages from mirror
# 4. System reboots into newly installed Debian
# We'll poll for SSH availability with the NEW password
# This indicates the new system has been installed and booted
MAX_WAIT=1800 # 30 minutes maximum wait
POLL_INTERVAL=30
ELAPSED=0
echo "Polling for new system availability (max wait: ${MAX_WAIT}s)..."
while [ $ELAPSED -lt $MAX_WAIT ]; do
echo "Attempt at ${ELAPSED}s: Trying to connect with new credentials..."
# Try to connect with the NEW password (set by debi.sh installation)
if sshpass -p 'newpass123' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10 -p 2222 ${{ matrix.user }}@localhost 'echo "NEW SYSTEM CONNECTED!"' 2>/dev/null; then
echo "✓ Successfully connected to newly installed system!"
echo "✓ Installation completed successfully!"
exit 0
fi
# Also check if old system is still responding (installation not started yet)
if sshpass -p 'rootpass123' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 -p 2222 root@localhost 'echo "old system"' 2>/dev/null; then
echo " Old system still responding - installation may not have started"
fi
sleep $POLL_INTERVAL
ELAPSED=$((ELAPSED + POLL_INTERVAL))
done
echo "✗ Timeout: Could not connect to new system after ${MAX_WAIT}s"
exit 1
- name: Verify new system installation
run: |
echo "Verifying the newly installed Debian system..."
# Connect with new credentials and verify system
sshpass -p 'newpass123' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 ${{ matrix.user }}@localhost << 'VERIFY_EOF'
echo "=== System Information ==="
uname -a
echo ""
echo "=== OS Release ==="
cat /etc/os-release
echo ""
echo "=== Disk Usage ==="
df -h
echo ""
echo "=== Memory Info ==="
free -h
echo ""
echo "=== Network Configuration ==="
ip addr show
echo ""
echo "=== Hostname ==="
hostname
echo ""
echo "✓ New Debian system is running successfully!"
VERIFY_EOF
- name: Collect debug information on failure
if: failure()
run: |
echo "=== Serial console log ==="
cat /tmp/serial.log || echo "No serial log available"
echo "=== Trying to connect with old credentials ==="
sshpass -p 'rootpass123' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 -p 2222 root@localhost \
'journalctl -n 100' 2>/dev/null || echo "Could not connect with old credentials"
echo "=== Trying to connect with new credentials ==="
sshpass -p 'newpass123' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 -p 2222 ${{ matrix.user }}@localhost \
'journalctl -n 100' 2>/dev/null || echo "Could not connect with new credentials"
- name: Shutdown VM
if: always()
run: |
# Try graceful shutdown with new credentials first (if new system is installed)
sshpass -p 'newpass123' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 -p 2222 ${{ matrix.user }}@localhost \
'sudo poweroff' 2>/dev/null || true
# Also try with old credentials (if still on old system)
sshpass -p 'rootpass123' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=5 -p 2222 root@localhost \
'poweroff' 2>/dev/null || true
sleep 5
# Force kill QEMU if still running
sudo pkill qemu-system-x86_64 || true
- name: Upload logs as artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: test-logs-v${{ matrix.version }}-${{ matrix.mirror }}-ethx${{ matrix.ethx }}-${{ matrix.user }}-nc${{ matrix.network_console }}
path: |
/tmp/debi-output.log
/tmp/serial.log
if-no-files-found: warn
Loading…
Cancel
Save