--- created: 2026-04-18 path: Sources/Dev project: pbsii-cicd-pipeline status: active tags: - pbs - pbsii - github-actions - ansible - cicd - docker - deployment type: project-plan updated: 2026-04-18 --- # PBSII CI/CD Pipeline (Revised) ## Goal Automate deployment of PBSII layers using GitHub Actions with a self-hosted runner on `ustest1`. Each layer deploys independently based on which files changed. Branch strategy controls target environment: `main` → production, `staging` → staging. Start with the dashboard layer, then extend to collector and parser. ## Context - PBSII is a private monorepo on GitHub with subdirectories: `collector/`, `parser/`, `dashboard/`, `n8n/` - Ansible project (`wp-i`) handles all server config and Docker deployments, lives on `ustest1` - Each Docker container has its own folder and `docker-compose.yml` on the server (e.g., `/opt/docker/dashboard/`) - Ansible docker role currently deploys all containers in a loop — migrating to per-container tags - A standalone pbs-hub deploy role exists as a reference pattern - Each PBSII layer deploys differently: dashboard is Docker, collector is systemd, parser is Python/venv - n8n workflows are version-controlled JSON — no CI/CD needed ## Architecture ``` GitHub Push (to dashboard/ on main) → GitHub signals self-hosted runner on ustest1 → ustest1 already has wp-i, SSH keys, Ansible — everything it needs → Runs Ansible playbook with dashboard tag → Ansible SSHs to Linode production, deploys dashboard → Google Chat notification on success/failure GitHub Push (to dashboard/ on staging) → Same flow, but Ansible targets staging server ``` ## Why Self-Hosted Runner - `ustest1` (Proxmox homelab) already has `wp-i`, SSH keys to Linode, and Ansible installed - No secrets to copy into GitHub cloud runners - No cloning the `wp-i` repo onto temporary VMs - No rebuilding Ansible environment on every run - Solves the staging drift problem — staging deployments go through the same Ansible path as production ## Phase 1: Self-Hosted Runner Setup ### 1a. Install GitHub Actions runner on ustest1 In the PBSII GitHub repo → Settings → Actions → Runners → New self-hosted runner: - [ ] Follow GitHub's instructions to download and configure the runner agent on `ustest1` - [ ] Install as a systemd service so it starts on boot - [ ] Verify runner shows as "Idle" in GitHub repo settings ### 1b. Runner user permissions - [ ] The runner agent runs as a user on `ustest1` — that user needs SSH access to Linode (production + staging) - [ ] Ensure that user has access to `wp-i` on `ustest1` - [ ] Ensure Ansible vault password is accessible (file or environment variable) ### 1c. Label the runner Add labels to the runner for targeting: - [ ] `self-hosted` (default) - [ ] `ustest1` (custom, for clarity) ## Phase 2: Ansible Dashboard Role ### 2a. Create the dashboard Ansible role in wp-i Use the existing pbs-hub deploy role as a reference pattern. The role should: - [ ] Copy dashboard source files to server at `/opt/docker/dashboard/` - [ ] Copy `docker-compose.yml` and `Dockerfile` to the server - [ ] Run `docker compose up -d --build` in the dashboard folder - [ ] Healthcheck to verify dashboard is responding ### 2b. Tag the role - [ ] Ensure the role can be called with `--tags dashboard` - [ ] Verify it only touches the dashboard container, nothing else ### 2c. Environment targeting The playbook needs to accept a variable (e.g., `target_env`) that determines whether it deploys to production or staging: - [ ] `target_env: production` → production Linode IP, production compose overrides - [ ] `target_env: staging` → staging Linode IP, staging compose overrides This keeps the same role for both environments — only the target changes. ## Phase 3: GitHub Actions Workflow (Dashboard) ### 3a. Production workflow File: `.github/workflows/deploy-dashboard.yml` ```yaml name: Deploy Dashboard on: push: branches: [main] paths: - 'dashboard/**' jobs: deploy: runs-on: [self-hosted, ustest1] steps: - name: Checkout PBSII uses: actions/checkout@v4 - name: Run Ansible playbook run: | cd /path/to/wp-i ansible-playbook playbook.yml \ --tags dashboard \ -e target_env=production - name: Notify Google Chat (success) if: success() run: | curl -X POST "$GOOGLE_CHAT_WEBHOOK" \ -H 'Content-Type: application/json' \ -d '{"text": "✅ Dashboard deployed to production (commit: ${{ github.sha }})" }' - name: Notify Google Chat (failure) if: failure() run: | curl -X POST "$GOOGLE_CHAT_WEBHOOK" \ -H 'Content-Type: application/json' \ -d '{"text": "❌ Dashboard deploy FAILED (commit: ${{ github.sha }})" }' env: GOOGLE_CHAT_WEBHOOK: ${{ secrets.GOOGLE_CHAT_WEBHOOK }} ``` ### 3b. Staging workflow File: `.github/workflows/deploy-dashboard-staging.yml` Same as above but: - Trigger: `branches: [staging]` - Ansible flag: `-e target_env=staging` - Notification says "staging" instead of "production" Alternatively, this could be one workflow file with a branch conditional — decide during implementation. ### 3c. Test the workflow - [ ] Push a small change to `dashboard/` on `staging` branch first - [ ] Watch the Actions tab in GitHub - [ ] Verify dashboard redeploys on staging server - [ ] Verify Google Chat notification arrives - [ ] Verify pushing to other directories does NOT trigger this workflow - [ ] Once staging is solid, test the `main` branch → production flow ## Phase 4: Extend to Other Layers Once the dashboard pipeline is proven, repeat the pattern: ### 4a. Collector pipeline File: `.github/workflows/deploy-collector.yml` Trigger: `paths: ['collector/**']` Ansible role needs to: - [ ] Cross-compile Go binary in CI step (or build on server) - [ ] Copy binary to server - [ ] Restart the systemd service (`systemctl restart pbs-collector`) - [ ] Verify collector responds on its HTTP API Note: Go cross-compiles easily — building on `ustest1` for Linux/amd64 target and shipping the binary to Linode avoids needing Go installed on production. ### 4b. Parser pipeline File: `.github/workflows/deploy-parser.yml` Trigger: `paths: ['parser/**']` Ansible role needs to: - [ ] Copy parser source to server - [ ] Ensure Python venv exists with correct deps (uv sync) - [ ] No restart needed — n8n triggers it on schedule ## Phase 5: Google Chat Notifications ### 5a. Setup - [ ] Add `GOOGLE_CHAT_WEBHOOK` to GitHub Secrets (can use same webhook as existing n8n alerts, or create a dedicated one for deployments) - [ ] Decide notification format — commit hash, branch, layer name, success/failure ### 5b. Notification content Success: `✅ [layer] deployed to [env] — commit abc1234` Failure: `❌ [layer] deploy FAILED on [env] — commit abc1234 — check Actions tab` ## Repo Structure After Implementation ``` pbsii/ ├── .github/ │ └── workflows/ │ ├── deploy-dashboard.yml │ ├── deploy-dashboard-staging.yml │ ├── deploy-collector.yml │ ├── deploy-collector-staging.yml │ ├── deploy-parser.yml │ └── deploy-parser-staging.yml ├── collector/ │ ├── README.md │ ├── go.mod │ ├── main.go │ ├── Makefile │ └── pbs-collector.service ├── parser/ │ ├── README.md │ ├── pyproject.toml │ └── parser.py ├── dashboard/ │ ├── README.md │ ├── pyproject.toml │ ├── Dockerfile │ ├── docker-compose.yml │ └── app.py ├── n8n/ │ └── workflows/ │ └── *.json ├── sql/ │ └── schema.sql └── README.md ``` Ansible roles live in `wp-i`, not in this repo: ``` wp-i/ └── roles/ ├── dashboard/ ├── collector/ └── parser/ ``` ## GitHub Secrets Required - [ ] `GOOGLE_CHAT_WEBHOOK` — webhook URL for deployment notifications - [ ] `ANSIBLE_VAULT_PASSWORD` — if vault is needed for deploy (may not be needed if runner user has access to vault password file on `ustest1`) Note: SSH keys and Ansible inventory are already on `ustest1` — no need to store them in GitHub. ## Open Questions - [ ] What is the path to `wp-i` on `ustest1`? Needed for the workflow `cd` step. - [ ] Which user does the runner agent run as on `ustest1`? That user needs SSH + Ansible access. - [ ] Does `ustest1` currently have SSH keys to both production and staging Linode? If not, set up during Phase 1. - [ ] One workflow file per layer per environment (6 files), or one per layer with branch conditionals (3 files)? Start simple, refactor if it gets noisy. - [ ] Google Chat: dedicated deployment webhook or reuse existing? ## Key Principles - Each layer deploys independently — a change to the parser never touches the dashboard - Ansible stays the deployment executor — GitHub Actions is just the trigger - Self-hosted runner on `ustest1` — no secrets in the cloud, no rebuilding environments - Branch strategy solves staging drift — `staging` branch → staging server, `main` → production - Build on server for v1 — move to CI builds later if needed - Google Chat notifications on every deploy — success and failure - Start with dashboard, prove the pattern, then extend - Test on staging first, always ...sent from Jenny & Travis