feat: add GitOps workflow — branch strategy, PR template, deploy/rollback scripts, CI skeleton
CI / validate (push) Failing after 11s
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:
@@ -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
|
||||||
@@ -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 -->
|
||||||
@@ -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
@@ -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
@@ -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`
|
||||||
Executable
+134
@@ -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
|
||||||
Executable
+22
@@ -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"
|
||||||
Reference in New Issue
Block a user