Files
brainwave/docs/superpowers/plans/2026-04-24-ansible-arm-deploy.md
T

6.7 KiB

ARM Deploy to OrangePi via Ansible — Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Add a Gitea Actions workflow that deploys the ARM64 Docker image to an OrangePi via Ansible after each successful release.

Architecture: A separate deploy.yml workflow triggers via workflow_run when the Release Pipeline succeeds. The runner acts as Ansible controller, downloads the ARM artifacts from the Gitea release, writes a dynamic inventory from secrets, and runs a playbook that copies files, loads the Docker image, and restarts the container on the OrangePi.

Tech Stack: Gitea Actions, Ansible (ansible-playbook), Docker, Docker Compose


File Map

File Action Responsibility
.gitea/workflows/deploy.yml Create Gitea workflow triggered by release pipeline success
ansible/deploy.yml Create Ansible playbook — copy artifacts, load image, restart container

Note: Inventory is written dynamically in the workflow from secrets (not committed) so no sensitive host details land in the repo.


Task 1: Ansible Playbook

Files:

  • Create: ansible/deploy.yml

  • Step 1: Create the playbook

Create ansible/deploy.yml with this exact content:

---
- name: Deploy Brainwave ARM to OrangePi
  hosts: orangepi
  gather_facts: false

  tasks:
    - name: Ensure deploy directory exists
      file:
        path: /home/user/brainwave
        state: directory
        mode: '0755'

    - name: Copy ARM tarball
      copy:
        src: "{{ artifact_path }}/brainwave-arm64.tar.gz"
        dest: /home/user/brainwave/brainwave-arm64.tar.gz

    - name: Copy docker-compose file
      copy:
        src: "{{ artifact_path }}/docker-compose.arm64.yml"
        dest: /home/user/brainwave/docker-compose.yml

    - name: Load Docker image
      command: docker load -i /home/user/brainwave/brainwave-arm64.tar.gz

    - name: Start container
      command: docker compose up -d
      args:
        chdir: /home/user/brainwave

artifact_path is passed in by the workflow via --extra-vars. The SSH user (ANSIBLE_USER) must be in the docker group on the OrangePi — if not, add become: true to the last two tasks and ensure ANSIBLE_USER has sudo access.

  • Step 2: Syntax-check the playbook locally

Run (requires ansible installed locally, skip if not available):

ansible-playbook ansible/deploy.yml --syntax-check -i "localhost," -e "artifact_path=/tmp"

Expected output: playbook: ansible/deploy.yml

If Ansible is not installed locally, skip this step — the workflow will catch syntax errors on first run.

  • Step 3: Commit the playbook
git add ansible/deploy.yml
git commit -m "feat: add Ansible playbook for ARM deploy to OrangePi"

Task 2: Deploy Workflow

Files:

  • Create: .gitea/workflows/deploy.yml

  • Step 1: Create the workflow file

Create .gitea/workflows/deploy.yml with this exact content:

name: Deploy ARM to OrangePi

on:
  workflow_run:
    workflows: ["Release Pipeline"]
    types:
      - completed

jobs:
  deploy:
    runs-on: ubuntu-latest
    if: github.event.workflow_run.conclusion == 'success'
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Install Ansible
        run: |
          sudo apt-get update -qq
          sudo apt-get install -y ansible

      - name: Write SSH Key
        run: |
          echo "${{ secrets.ANSIBLE_SSH_KEY }}" > /tmp/ansible_key
          chmod 600 /tmp/ansible_key

      - name: Write Inventory
        run: |
          printf '[orangepi]\norangepi ansible_host=%s ansible_user=%s ansible_ssh_private_key_file=/tmp/ansible_key ansible_ssh_common_args="-o StrictHostKeyChecking=no"\n' \
            "${{ secrets.ANSIBLE_HOST }}" \
            "${{ secrets.ANSIBLE_USER }}" > /tmp/inventory.ini

      - name: Download ARM Release Artifacts
        run: |
          TAG="${{ github.event.workflow_run.head_branch }}"
          BASE_URL="${{ github.server_url }}/${{ github.repository }}/releases/download/${TAG}"
          wget -q "${BASE_URL}/brainwave-arm64.tar.gz"
          wget -q "${BASE_URL}/docker-compose.arm64.yml"

      - name: Deploy via Ansible
        run: |
          ansible-playbook ansible/deploy.yml \
            -i /tmp/inventory.ini \
            -e "artifact_path=${{ github.workspace }}"

      - name: Cleanup SSH Key
        if: always()
        run: rm -f /tmp/ansible_key
  • Step 2: Validate the YAML syntax
python3 -c "import yaml; yaml.safe_load(open('.gitea/workflows/deploy.yml'))" && echo "YAML valid"

Expected output: YAML valid

  • Step 3: Commit the workflow
git add .gitea/workflows/deploy.yml
git commit -m "feat: add deploy workflow to push ARM release to OrangePi via Ansible"

Task 3: Add Gitea Secrets

Files: None (Gitea UI configuration)

  • Step 1: Open your Gitea repo secrets

Navigate to: Settings → Secrets and Variables → Actions → New Secret

  • Step 2: Add the three required secrets

Add each of these:

Name Value
ANSIBLE_HOST IP address or hostname of your OrangePi (e.g. 192.168.1.50)
ANSIBLE_USER SSH username on the OrangePi (e.g. orangepi or user)
ANSIBLE_SSH_KEY Full content of the private SSH key (including -----BEGIN... and -----END... lines)

The SSH public key must already be in ~/.ssh/authorized_keys on the OrangePi for ANSIBLE_USER.

  • Step 3: Verify the OrangePi SSH user is in the docker group

SSH into the OrangePi and run:

groups $USER

If docker is not listed, add it and re-login:

sudo usermod -aG docker $USER

Task 4: End-to-End Verification

  • Step 1: Push a version tag to trigger the release pipeline
git tag v0.0.1-test
git push origin v0.0.1-test
  • Step 2: Monitor the Release Pipeline run

In Gitea: Actions → Release Pipeline — wait for it to complete successfully. Confirm the release assets (brainwave-arm64.tar.gz, docker-compose.arm64.yml) appear under Releases → v0.0.1-test.

  • Step 3: Monitor the Deploy ARM to OrangePi run

In Gitea: Actions → Deploy ARM to OrangePi — confirm the workflow triggers after the release pipeline completes and all steps pass.

  • Step 4: Verify deployment on the OrangePi

SSH into the OrangePi and run:

docker ps

Expected: a brainwave container in Up state with port 3001 mapped.

curl http://localhost:3001

Expected: a response from the Brainwave app (not a connection refused error).