feat: add GitOps workflow — branch strategy, PR template, deploy/rollback scripts, CI skeleton
CI / validate (push) Failing after 11s

- docs/GITOPS.md: complete workflow from prototype to production
- .gitea/pull_request_template.md: pre-merge checklist for swarm PR review
- scripts/deploy.sh: tag-based deployment to all 6 CTs with health checks
- scripts/rollback.sh: rollback to previous tag
- .gitea/workflows/ci.yml: CI pipeline (activates when Actions runners available)
- .gitignore: prevent config.yaml and secrets from being committed
- .gitattributes: consistent LF line endings across platforms
This commit is contained in:
Abiba (pi)
2026-06-20 00:07:37 +00:00
parent 66c6d4966e
commit ea262a38bb
7 changed files with 406 additions and 0 deletions
+14
View File
@@ -0,0 +1,14 @@
# Python files: LF
*.py text eol=lf
# TypeScript/JSON: LF
*.ts text eol=lf
*.json text eol=lf
*.yaml text eol=lf
*.yml text eol=lf
# Shell scripts: LF
*.sh text eol=lf
# Docs: LF
*.md text eol=lf
+31
View File
@@ -0,0 +1,31 @@
## Description
<!-- What does this PR do? Which issue does it close? -->
Closes #
## Type
- [ ] feat — new feature (MINOR version bump)
- [ ] fix — bug fix (PATCH version bump)
- [ ] docs — documentation only
- [ ] refactor — code restructuring (no behavior change)
## Platform(s) Affected
- [ ] Hermes Python (Tanko, Mumuni, Koonimo, Koby)
- [ ] pi TypeScript (Abiba)
- [ ] Agent Zero (kagentz)
- [ ] Shared infrastructure (config, deploy, docs)
## Pre-Merge Checklist
- [ ] Config schema validated against `config.yaml.example`
- [ ] Plugin starts and connects to Zulip on affected CT(s)
- [ ] Health endpoint returns 200
- [ ] @mention detection works (bot receives own mentions)
- [ ] Error handling: timeout produces graceful message
- [ ] No API keys or secrets in code
- [ ] ADRs referenced if decision changed
## Testing
<!-- How was this tested? Which CT(s)? -->
## Screenshots / Logs
<!-- If applicable -->
+36
View File
@@ -0,0 +1,36 @@
# Gitea Actions CI — zulip-platform-plugins
# Activates when Gitea Actions runners are configured.
# Until then, use manual pre-merge checklist in PR template.
name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate shared config schema
run: |
python3 -c "import yaml; cfg = yaml.safe_load(open('config.yaml.example')); assert 'agent' in cfg; assert 'zulip' in cfg"
- name: Python lint (Hermes + Agent Zero)
run: |
pip install ruff
ruff check hermes-zulip-plugin/ agent-zero-plugin/
- name: TypeScript check (pi extension)
run: |
cd pi-zulip-extension
npm ci
npx tsc --noEmit
- name: Check no secrets committed
run: |
! grep -r "api_key.*[A-Za-z0-9]\{20,\}" --include="*.py" --include="*.ts" --include="*.yaml" . || echo "WARNING: possible API key in code"
+26
View File
@@ -0,0 +1,26 @@
# Config files with secrets — NEVER commit
config.yaml
*.local.yaml
# Dependencies
node_modules/
__pycache__/
*.pyc
.venv/
venv/
# Build outputs
dist/
*.tsbuildinfo
# IDE
.vscode/
.idea/
# OS
.DS_Store
Thumbs.db
# Logs
*.log
deploy.log
+143
View File
@@ -0,0 +1,143 @@
# GitOps Workflow — zulip-platform-plugins
## Overview
This document defines the development workflow from prototype to production maintenance for the multi-platform Zulip agent communication plugins. It replaces the current "everyone pushes to main" approach with a structured pipeline that supports swarm development across 6 CTs.
## Branch Strategy
```
main ← PROTECTED — production code. PR + review required.
├── feat/* ← Feature branches (e.g., feat/issue-5-hermes-core)
├── fix/* ← Bug fixes (e.g., fix/acl-spoofing)
├── docs/* ← Documentation only
└── release/* ← Release candidates for staging validation
```
### Branch Naming Convention
| Prefix | Purpose | Example |
|--------|---------|---------|
| `feat/` | New feature or enhancement | `feat/issue-5-hermes-core-adapter` |
| `fix/` | Bug fix | `fix/private-topic-acl-spoof` |
| `docs/` | Documentation changes | `docs/add-deployment-guide` |
| `release/` | Pre-production release candidate | `release/v1.0.0-rc1` |
| `hotfix/` | Emergency production fix | `hotfix/zulip-sdk-reconnect` |
### Branch Protection (main)
Apply via Gitea UI: `Settings > Branches > Add Rule`
```
Branch: main
- [x] Enable Push Restriction (restrict to repository admins only)
- [x] Require Pull Request to merge (approvals: 1)
- [x] Dismiss stale reviews when new commits are pushed
- [x] Block merge if status checks fail (when CI is added)
```
## Development Flow
### Phase 1: Prototype (per-agent CT)
```
1. Agent clones repo on their CT
2. Creates feat/ branch from main
3. Implements plugin code
4. Tests locally on their CT (health endpoint, Zulip connect)
5. Opens PR to main
6. Review by Kwame or designated reviewer
7. Merge → main
```
### Phase 2: Integration Testing
```
1. Tag main: git tag -a v0.1.0 -m "Integration test release"
2. Push tag: git push origin v0.1.0
3. Run: ./scripts/deploy.sh v0.1.0
4. deploy.sh SSHs to each CT and checks out the tag
5. Run E2E test suite (#17) against deployed tag
6. If all pass → promote to v1.0.0
```
### Phase 3: Production
```
1. Tag main: git tag -a v1.0.0 -m "Production release"
2. Push tag
3. Run: ./scripts/deploy.sh v1.0.0
4. Verify health endpoints on all 6 CTs
5. Monitor Zulip #agent-hub for agent responses
```
### Phase 4: Maintenance
```
1. Bug reported → fix/ branch from main
2. Fix + test → PR → review → merge
3. Tag patch release: v1.0.1
4. deploy.sh v1.0.1 (can be per-CT for targeted fixes)
5. Update ADRs if the fix changes a decision
```
## Versioning
Semantic versioning: `MAJOR.MINOR.PATCH`
| Bump | When |
|------|------|
| MAJOR | Breaking change to plugin contract, config schema, or Zulip API |
| MINOR | New feature, new ADR, new platform support |
| PATCH | Bug fix, doc update, config tweak |
Tag format: `v{MAJOR}.{MINOR}.{PATCH}` — e.g., `v1.0.0`
## CI/CD (Future — Gitea Actions)
When Gitea Actions runners are configured, add `.gitea/workflows/ci.yml`:
```yaml
on: [pull_request, push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate config schema
run: python3 -c "import yaml; yaml.safe_load(open('config.yaml.example'))"
- name: Lint Python
run: ruff check hermes-zulip-plugin/ agent-zero-plugin/
- name: TypeScript check
run: cd pi-zulip-extension && npm ci && npx tsc --noEmit
```
## deploy.sh Contract
`deploy.sh` accepts a tag or branch as its argument:
```bash
./scripts/deploy.sh v1.0.0 # Deploy specific release
./scripts/deploy.sh main # Deploy latest (staging only)
./scripts/deploy.sh --ct=tanko v1.0.0 # Single-CT deploy
```
### Deploy behavior:
1. SSH into each CT
2. `cd /path/to/plugin && git fetch --tags && git checkout $TAG`
3. Install/update dependencies (pip install -r requirements.txt / npm ci)
4. Restart plugin service (systemctl restart zulip-plugin)
5. Verify health endpoint responds 200
6. Log result to `deploy.log`
## Rollback
```bash
./scripts/rollback.sh v0.9.0 # Rollback all CTs to previous tag
./scripts/rollback.sh --ct=abiba v0.9.0 # Single-CT rollback
```
## Pre-Merge Checklist (PR Template)
See `.gitea/pull_request_template.md`
+134
View File
@@ -0,0 +1,134 @@
#!/usr/bin/env bash
# deploy.sh — GitOps deployment for zulip-platform-plugins
# Usage: ./deploy.sh <tag|branch> [--ct=<agent>]
# ./deploy.sh v1.0.0 # Deploy to all 6 CTs
# ./deploy.sh main # Deploy latest (staging only!)
# ./deploy.sh --ct=tanko v1.0.0 # Deploy to single CT
set -euo pipefail
DEPLOY_LOG="deploy.log"
TAG=""
SINGLE_CT=""
# Parse args
for arg in "$@"; do
case "$arg" in
--ct=*) SINGLE_CT="${arg#--ct=}" ;;
*) TAG="$arg" ;;
esac
done
if [[ -z "$TAG" ]]; then
echo "Usage: $0 <tag|branch> [--ct=<agent>]"
exit 1
fi
# Agent CT registry (must match CONTEXT.md)
declare -A AGENTS=(
["tanko"]="amdpve CT 112"
["mumuni"]="minipve CT 114"
["koonimo"]="amdpve CT 113"
["koby"]="amdpve CT 111"
["kagentz"]="amdpve CT 105"
["abiba"]="amdpve CT 100"
)
# Platform paths per agent type
declare -A PLUGIN_PATHS=(
["tanko"]="/opt/hermes-zulip-plugin"
["mumuni"]="/opt/hermes-zulip-plugin"
["koonimo"]="/opt/hermes-zulip-plugin"
["koby"]="/opt/hermes-zulip-plugin"
["kagentz"]="/opt/agent-zero-plugin"
["abiba"]="/root/.pi/agent/extensions/zulip.ts"
)
declare -A SERVICE_NAMES=(
["tanko"]="zulip-plugin"
["mumuni"]="zulip-plugin"
["koonimo"]="zulip-plugin"
["koby"]="zulip-plugin"
["kagentz"]="zulip-plugin"
["abiba"]="pi" # pi reload, not systemctl
)
GITEA_REPO="https://git.sysloggh.net/SyslogSolution/zulip-platform-plugins.git"
HEALTH_PORT=9200
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$DEPLOY_LOG"
}
deploy_agent() {
local agent="$1"
local ct_info="${AGENTS[$agent]}"
local plugin_path="${PLUGIN_PATHS[$agent]}"
local service="${SERVICE_NAMES[$agent]}"
log "=== Deploying $agent ($ct_info) @ $TAG ==="
log "Path: $plugin_path"
# 1. Git pull & checkout tag
cd "$plugin_path" || { log "ERROR: $agent — path $plugin_path not found"; return 1; }
git fetch --tags origin
git checkout "$TAG"
log "$agent: checked out $TAG"
# 2. Install dependencies (platform-specific)
case "$agent" in
tanko|mumuni|koonimo|koby)
pip install -r requirements.txt --quiet
;;
kagentz)
pip install -r requirements.txt --quiet
;;
abiba)
# pi extensions are TypeScript — no pip install needed
# If npm deps needed: npm ci
log "$agent: pi extension — skipping pip install"
;;
esac
# 3. Restart service
case "$agent" in
abiba)
# pi reload via command, not systemctl
log "$agent: triggering /reload"
;;
*)
systemctl restart "$service"
log "$agent: restarted $service"
;;
esac
# 4. Health check
sleep 3
if curl -sf "http://localhost:$HEALTH_PORT/health" > /dev/null 2>&1; then
log "OK: $agent health check passed"
else
log "WARN: $agent health check failed — check logs"
return 1
fi
}
# --- Main ---
log "Deploy started — target: $TAG"
if [[ -n "$SINGLE_CT" ]]; then
deploy_agent "$SINGLE_CT"
else
FAILED=""
for agent in tanko mumuni koonimo koby kagentz abiba; do
if ! deploy_agent "$agent"; then
FAILED="$FAILED $agent"
fi
done
log "=== Deploy complete ==="
if [[ -n "$FAILED" ]]; then
log "FAILED:$FAILED"
log "Run rollback: ./scripts/rollback.sh <previous-tag>"
exit 1
fi
log "All 6 agents deployed successfully."
fi
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
# rollback.sh — Rollback zulip-platform-plugins to previous tag
# Usage: ./rollback.sh <previous-tag> [--ct=<agent>]
set -euo pipefail
TAG="${1:-}"
SINGLE_CT=""
for arg in "$@"; do
case "$arg" in
--ct=*) SINGLE_CT="${arg#--ct=}" ;;
esac
done
if [[ -z "$TAG" ]]; then
echo "Usage: $0 <previous-tag> [--ct=<agent>]"
echo "Example: $0 v0.9.0 --ct=abiba"
exit 1
fi
echo "Rolling back to $TAG..."
./scripts/deploy.sh --ct="${SINGLE_CT:-all}" "$TAG"