From 1e4f1f5fb3ad88c8447f5a031dfcc9f788ccaab0 Mon Sep 17 00:00:00 2001 From: torlando-tech Date: Fri, 7 Nov 2025 22:31:22 -0500 Subject: [PATCH] ci: Add GitHub Actions workflow for automated Pi deployment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds continuous deployment workflow that automatically deploys code changes to Raspberry Pi devices after tests pass. Features: - Runs on self-hosted runner after unit/integration tests complete - Supports containerized runners (k3s/Docker) via SSH key secrets - Deploys to multiple Pis in sequence with detailed logging - Automatically restarts rnsd service after code update - Fails entire job if any Pi deployment fails Required secrets: PI_HOSTS, PI_REPO_PATH, PI_USER, PI_SSH_KEY 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/deploy.yml | 193 +++++++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..5fc82f1 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,193 @@ +name: Deploy to Raspberry Pi + +on: + push: + branches: [ "*" ] + paths: + - 'src/**' + - '.github/workflows/deploy.yml' + +jobs: + deploy: + name: Deploy to Raspberry Pis + runs-on: self-hosted + needs: [unit-tests, integration-tests] + # Only run if tests exist and passed (skip if no Python changes detected) + if: always() && (needs.unit-tests.result == 'success' || needs.unit-tests.result == 'skipped') && (needs.integration-tests.result == 'success' || needs.integration-tests.result == 'skipped') + + steps: + - name: Validate required secrets + run: | + if [ -z "${{ secrets.PI_HOSTS }}" ]; then + echo "Error: PI_HOSTS secret is not set" + echo "Please set PI_HOSTS secret with comma-separated hostnames (e.g., 'pi1.local,pi2.local')" + exit 1 + fi + if [ -z "${{ secrets.PI_REPO_PATH }}" ]; then + echo "Error: PI_REPO_PATH secret is not set" + echo "Please set PI_REPO_PATH secret with repository path (e.g., '/home/pi/ble-reticulum')" + exit 1 + fi + if [ -z "${{ secrets.PI_USER }}" ]; then + echo "Error: PI_USER secret is not set" + echo "Please set PI_USER secret with SSH username (e.g., 'pi')" + exit 1 + fi + if [ -z "${{ secrets.PI_SSH_KEY }}" ]; then + echo "Error: PI_SSH_KEY secret is not set" + echo "Please set PI_SSH_KEY secret with SSH private key for Pi access" + exit 1 + fi + echo "All required secrets are configured" + + - name: Setup SSH key + env: + PI_SSH_KEY: ${{ secrets.PI_SSH_KEY }} + run: | + # Create .ssh directory if it doesn't exist + mkdir -p ~/.ssh + chmod 700 ~/.ssh + + # Write SSH private key to file + echo "$PI_SSH_KEY" > ~/.ssh/id_ed25519 + chmod 600 ~/.ssh/id_ed25519 + + # Disable strict host key checking for known local hosts + cat >> ~/.ssh/config <>> Deploying to $HOST..." + + # Deploy with error handling + if ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 "$PI_USER@$HOST" bash </dev/null; then + sudo systemctl restart rnsd || exit 1 + echo " ✓ rnsd restarted via systemd" + else + # Kill existing rnsd processes + pkill -9 rnsd 2>/dev/null || true + sleep 1 + # Start rnsd + nohup rnsd > /dev/null 2>&1 & + sleep 2 + # Verify rnsd is running + if pgrep -x rnsd > /dev/null; then + echo " ✓ rnsd started successfully" + else + echo " ✗ Failed to start rnsd" + exit 1 + fi + fi + + echo " ✓ Deployment successful!" +EOF + then + echo "✓ Successfully deployed to $HOST" + SUCCESSFUL_HOSTS+=("$HOST") + else + echo "✗ Failed to deploy to $HOST" + FAILED_HOSTS+=("$HOST") + fi + + echo "" + done + + # Print summary + echo "===================================" + echo "Deployment Summary" + echo "===================================" + echo "Successful: ${#SUCCESSFUL_HOSTS[@]}/${#HOSTS[@]}" + if [ ${#SUCCESSFUL_HOSTS[@]} -gt 0 ]; then + printf ' ✓ %s\n' "${SUCCESSFUL_HOSTS[@]}" + fi + + if [ ${#FAILED_HOSTS[@]} -gt 0 ]; then + echo "" + echo "Failed: ${#FAILED_HOSTS[@]}/${#HOSTS[@]}" + printf ' ✗ %s\n' "${FAILED_HOSTS[@]}" + echo "" + echo "===================================" + exit 1 + fi + + echo "===================================" + + - name: Cleanup SSH key + if: always() + run: | + # Remove SSH key for security + rm -f ~/.ssh/id_ed25519 + echo "SSH key cleaned up" + + - name: Deployment status + if: always() + run: | + echo "## Deployment Results" >> $GITHUB_STEP_SUMMARY + echo "- **Branch:** ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY + echo "- **Commit:** ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY + echo "- **Triggered by:** ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + if [ "${{ job.status }}" == "success" ]; then + echo "✓ All Raspberry Pis deployed successfully" >> $GITHUB_STEP_SUMMARY + else + echo "✗ Deployment failed on one or more Raspberry Pis" >> $GITHUB_STEP_SUMMARY + echo "Check the job logs for details" >> $GITHUB_STEP_SUMMARY + fi