diff --git a/.github/scripts/test-qemu.py b/.github/scripts/test-qemu.py new file mode 100755 index 0000000..7796ec4 --- /dev/null +++ b/.github/scripts/test-qemu.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +"""Boot a Buildroot QEMU image and validate the ShellHub agent installation.""" + +import sys +import os +import pexpect + +TIMEOUT_BOOT = 180 +TIMEOUT_CMD = 30 +LOG_FILE = "/tmp/qemu-test.log" + + +def main(): + if len(sys.argv) != 2: + print(f"Usage: {sys.argv[0]} ") + sys.exit(1) + + images_dir = sys.argv[1] + bzimage = os.path.join(images_dir, "bzImage") + rootfs = os.path.join(images_dir, "rootfs.ext2") + + for path in (bzimage, rootfs): + if not os.path.exists(path): + print(f"ERROR: {path} not found") + sys.exit(1) + + qemu_cmd = ( + f"qemu-system-x86_64" + f" -M pc" + f" -kernel {bzimage}" + f" -drive file={rootfs},if=virtio,format=raw" + f" -append \"rootwait root=/dev/vda console=ttyS0\"" + f" -nographic" + f" -no-reboot" + ) + + print(f"Starting QEMU: {qemu_cmd}") + logfile = open(LOG_FILE, "wb") + child = pexpect.spawn(qemu_cmd, timeout=TIMEOUT_BOOT, logfile=logfile, encoding=None) + + tests_passed = 0 + tests_total = 3 + + try: + # Wait for login prompt + print("Waiting for login prompt...") + child.expect(b"login:", timeout=TIMEOUT_BOOT) + child.sendline(b"root") + + # Wait for shell prompt + child.expect(b"#", timeout=TIMEOUT_CMD) + print("Logged in as root.") + + # Test 1: Binary exists and is executable + print("Test 1: Checking binary exists...") + child.sendline(b"test -x /usr/bin/shellhub-agent && echo TEST1_PASS || echo TEST1_FAIL") + child.expect(b"TEST1_(PASS|FAIL)", timeout=TIMEOUT_CMD) + if child.match.group(1) == b"PASS": + print(" PASS: /usr/bin/shellhub-agent exists and is executable") + tests_passed += 1 + else: + print(" FAIL: /usr/bin/shellhub-agent not found or not executable") + + child.expect(b"#", timeout=TIMEOUT_CMD) + + # Test 2: Binary runs (--version or --help) + print("Test 2: Checking binary runs...") + child.sendline(b"shellhub-agent --version && echo TEST2_PASS || echo TEST2_FAIL") + child.expect(b"TEST2_(PASS|FAIL)", timeout=TIMEOUT_CMD) + if child.match.group(1) == b"PASS": + print(" PASS: shellhub-agent --version works") + tests_passed += 1 + else: + print(" FAIL: shellhub-agent --version failed") + + child.expect(b"#", timeout=TIMEOUT_CMD) + + # Test 3: Init script installed + print("Test 3: Checking init script...") + child.sendline(b"test -f /etc/init.d/S42shellhub && echo TEST3_PASS || echo TEST3_FAIL") + child.expect(b"TEST3_(PASS|FAIL)", timeout=TIMEOUT_CMD) + if child.match.group(1) == b"PASS": + print(" PASS: /etc/init.d/S42shellhub exists") + tests_passed += 1 + else: + print(" FAIL: /etc/init.d/S42shellhub not found") + + child.expect(b"#", timeout=TIMEOUT_CMD) + + # Shutdown + print("Shutting down...") + child.sendline(b"poweroff") + child.expect(pexpect.EOF, timeout=60) + + except pexpect.TIMEOUT: + print("ERROR: Timeout waiting for QEMU") + child.close(force=True) + logfile.close() + sys.exit(1) + except pexpect.EOF: + print("ERROR: QEMU exited unexpectedly") + logfile.close() + sys.exit(1) + + logfile.close() + + print(f"\nResults: {tests_passed}/{tests_total} tests passed") + if tests_passed < tests_total: + sys.exit(1) + + print("All tests passed!") + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a5540a1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,136 @@ +name: CI + +on: + pull_request: + paths: + - 'package/shellhub/**' + - 'rootfs_overlay/**' + - 'Config.in' + - 'external.mk' + - '.github/**' + +permissions: + contents: write + pull-requests: write + +jobs: + update-hash: + runs-on: ubuntu-latest + if: github.actor == 'renovate[bot]' + + steps: + - name: Checkout PR + uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Update hash + run: | + VERSION=$(grep "^SHELLHUB_VERSION" package/shellhub/shellhub.mk | cut -d= -f2 | tr -d '[:space:]') + URL="https://github.com/shellhub-io/shellhub/releases/download/v${VERSION}/shellhub-agent.tar.gz" + + echo "Downloading ${URL}..." + curl -L -o /tmp/shellhub-agent.tar.gz "${URL}" + + NEW_MD5=$(md5sum /tmp/shellhub-agent.tar.gz | awk '{print $1}') + CURRENT_MD5=$(awk '{print $2}' package/shellhub/shellhub.hash 2>/dev/null || echo "") + rm /tmp/shellhub-agent.tar.gz + + if [ "$NEW_MD5" = "$CURRENT_MD5" ]; then + echo "Hash already up to date, skipping commit." + exit 0 + fi + + echo "md5 ${NEW_MD5} shellhub-agent.tar.gz" > package/shellhub/shellhub.hash + echo "Hash file updated successfully!" + + - name: Commit hash changes + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add package/shellhub/shellhub.hash + git diff --staged --quiet || git commit -m "Update hash for new version" + git push + + build-and-test: + needs: [update-hash] + if: always() && (needs.update-hash.result == 'success' || needs.update-hash.result == 'skipped') + runs-on: ubuntu-latest + timeout-minutes: 120 + + steps: + - name: Checkout PR + uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential libncurses-dev bc python3 rsync cpio unzip wget file \ + qemu-system-x86 python3-pexpect + + - name: Clone Buildroot + run: | + git clone --depth 1 --branch 2026.02 https://github.com/buildroot/buildroot.git /tmp/buildroot + + - name: Cache Buildroot downloads + uses: actions/cache@v4 + with: + path: /tmp/buildroot/dl + key: buildroot-dl-${{ hashFiles('package/shellhub/shellhub.mk') }} + restore-keys: buildroot-dl- + + - name: Cache ccache + uses: actions/cache@v4 + with: + path: ~/.buildroot-ccache + key: buildroot-ccache-${{ github.run_id }} + restore-keys: buildroot-ccache- + + - name: Configure Buildroot + working-directory: /tmp/buildroot + run: | + make BR2_EXTERNAL=$GITHUB_WORKSPACE qemu_x86_64_defconfig + cat >> .config << 'EOF' + BR2_TOOLCHAIN_EXTERNAL=y + BR2_TOOLCHAIN_EXTERNAL_BOOTLIN=y + BR2_TOOLCHAIN_EXTERNAL_BOOTLIN_X86_64_CORE_I7_GLIBC_STABLE=y + BR2_PACKAGE_SHELLHUB=y + BR2_CCACHE=y + BR2_CCACHE_DIR="/home/runner/.buildroot-ccache" + EOF + make olddefconfig + + - name: Build + working-directory: /tmp/buildroot + run: make -j$(nproc) + + - name: Test with QEMU + run: python3 .github/scripts/test-qemu.py /tmp/buildroot/output/images + + - name: Upload logs on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: qemu-test-logs + path: /tmp/qemu-test.log + + auto-merge: + needs: [build-and-test] + runs-on: ubuntu-latest + if: github.actor == 'renovate[bot]' + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Auto-approve + uses: hmarr/auto-approve-action@v4 + + - name: Auto-merge + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh pr merge ${{ github.event.pull_request.number }} --squash --auto diff --git a/.github/workflows/update-hash.yml b/.github/workflows/update-hash.yml deleted file mode 100644 index d4a4ee9..0000000 --- a/.github/workflows/update-hash.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Update hash - -on: - pull_request: - paths: - - 'package/shellhub/shellhub.mk' - -jobs: - update-hash: - runs-on: ubuntu-latest - if: github.actor == 'renovate[bot]' - - steps: - - name: Checkout PR - uses: actions/checkout@v4 - with: - ref: ${{ github.head_ref }} - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Update hash - run: | - # Read version from shellhub.mk - VERSION=$(grep "^SHELLHUB_VERSION" package/shellhub/shellhub.mk | cut -d= -f2 | tr -d '[:space:]') - - # Construct download URL - URL="https://github.com/shellhub-io/shellhub/releases/download/v${VERSION}/shellhub-agent.tar.gz" - - # Download the file - echo "Downloading ${URL}..." - curl -L -o /tmp/shellhub-agent.tar.gz "${URL}" - - # Calculate MD5 - MD5=$(md5sum /tmp/shellhub-agent.tar.gz | awk '{print $1}') - - # Generate hash file - echo "md5 ${MD5} shellhub-agent.tar.gz" > package/shellhub/shellhub.hash - - # Cleanup - rm /tmp/shellhub-agent.tar.gz - - echo "hash file updated successfully!" - - - name: Commit hash changes - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add package/shellhub/shellhub.hash - git diff --staged --quiet || git commit -m "Update hash for new version" - git push