From 76c1fd6347b92b89a0634855381fd2d30ce75a47 Mon Sep 17 00:00:00 2001 From: LaurensBartelds Date: Fri, 24 Apr 2026 08:43:33 +0200 Subject: [PATCH] docs: add implementation plan for ARM deploy to OrangePi via Ansible Co-Authored-By: Claude Sonnet 4.6 --- .../plans/2026-04-24-ansible-arm-deploy.md | 232 ++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-24-ansible-arm-deploy.md diff --git a/docs/superpowers/plans/2026-04-24-ansible-arm-deploy.md b/docs/superpowers/plans/2026-04-24-ansible-arm-deploy.md new file mode 100644 index 0000000..faa2a19 --- /dev/null +++ b/docs/superpowers/plans/2026-04-24-ansible-arm-deploy.md @@ -0,0 +1,232 @@ +# 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: + +```yaml +--- +- 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): + +```bash +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** + +```bash +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: + +```yaml +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** + +```bash +python3 -c "import yaml; yaml.safe_load(open('.gitea/workflows/deploy.yml'))" && echo "YAML valid" +``` + +Expected output: `YAML valid` + +- [ ] **Step 3: Commit the workflow** + +```bash +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: + +```bash +groups $USER +``` + +If `docker` is not listed, add it and re-login: + +```bash +sudo usermod -aG docker $USER +``` + +--- + +### Task 4: End-to-End Verification + +- [ ] **Step 1: Push a version tag to trigger the release pipeline** + +```bash +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: + +```bash +docker ps +``` + +Expected: a `brainwave` container in `Up` state with port `3001` mapped. + +```bash +curl http://localhost:3001 +``` + +Expected: a response from the Brainwave app (not a connection refused error).