[ci skip] add Forgejo task pipeline for OpenClaw AI agent
Forgejo issues as a task queue for OpenClaw: - Forgejo OAuth2 with Authentik SSO, self-registration disabled - Webhook-triggered task processing (instant) + CronJob backup (5min poll) - Tasks processed via Mistral Large 3 (NVIDIA NIM API) - Results posted as issue comments, auto-labeled and closed - Comment follow-ups and reopened issues supported - n8n RBAC for OpenClaw pod exec (future workflow integration)
This commit is contained in:
parent
c2765e890b
commit
76a4987eef
5 changed files with 842 additions and 0 deletions
231
scripts/setup-task-pipeline.sh
Executable file
231
scripts/setup-task-pipeline.sh
Executable file
|
|
@ -0,0 +1,231 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Setup script for the Forgejo task ingestion pipeline.
|
||||||
|
# Creates Authentik OAuth2 provider/application, configures Forgejo OAuth2 auth source,
|
||||||
|
# creates "tasks" repo, and sets up webhook to n8n.
|
||||||
|
#
|
||||||
|
# Prerequisites:
|
||||||
|
# - Authentik admin API token
|
||||||
|
# - Forgejo admin API token (create at https://forgejo.viktorbarzin.me/user/settings/applications)
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# AUTHENTIK_TOKEN="..." FORGEJO_TOKEN="..." bash scripts/setup-task-pipeline.sh
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
AUTHENTIK_URL="${AUTHENTIK_URL:-https://authentik.viktorbarzin.me}"
|
||||||
|
FORGEJO_URL="${FORGEJO_URL:-https://forgejo.viktorbarzin.me}"
|
||||||
|
N8N_WEBHOOK_URL="${N8N_WEBHOOK_URL:-https://n8n.viktorbarzin.me/webhook/forgejo-tasks}"
|
||||||
|
FORGEJO_ADMIN_USER="${FORGEJO_ADMIN_USER:-viktor}"
|
||||||
|
|
||||||
|
: "${AUTHENTIK_TOKEN:?Set AUTHENTIK_TOKEN (Authentik admin API token)}"
|
||||||
|
: "${FORGEJO_TOKEN:?Set FORGEJO_TOKEN (Forgejo admin API token)}"
|
||||||
|
|
||||||
|
ak_api() { curl -sf -H "Authorization: Bearer $AUTHENTIK_TOKEN" -H "Content-Type: application/json" "$@"; }
|
||||||
|
fg_api() { curl -sf -H "Authorization: token $FORGEJO_TOKEN" -H "Content-Type: application/json" "$@"; }
|
||||||
|
|
||||||
|
echo "=== Step 1: Create Authentik group 'Task Submitters' ==="
|
||||||
|
GROUP_RESP=$(ak_api "$AUTHENTIK_URL/api/v3/core/groups/" -d '{
|
||||||
|
"name": "Task Submitters",
|
||||||
|
"is_superuser": false,
|
||||||
|
"parent": null
|
||||||
|
}' 2>/dev/null) || {
|
||||||
|
echo " Group may already exist, checking..."
|
||||||
|
GROUP_RESP=$(ak_api "$AUTHENTIK_URL/api/v3/core/groups/?name=Task+Submitters" | python3 -c "import sys,json; r=json.load(sys.stdin)['results']; print(json.dumps(r[0]) if r else '')")
|
||||||
|
if [ -z "$GROUP_RESP" ]; then echo "ERROR: Failed to create or find group"; exit 1; fi
|
||||||
|
}
|
||||||
|
GROUP_PK=$(echo "$GROUP_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['pk'])")
|
||||||
|
echo " Group PK: $GROUP_PK"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Step 2: Create Authentik OAuth2 Provider for Forgejo ==="
|
||||||
|
# Find the explicit consent authorization flow
|
||||||
|
AUTH_FLOW=$(ak_api "$AUTHENTIK_URL/api/v3/flows/instances/?designation=authorization&search=explicit" | \
|
||||||
|
python3 -c "import sys,json; r=json.load(sys.stdin)['results']; print(r[0]['pk'] if r else '')")
|
||||||
|
if [ -z "$AUTH_FLOW" ]; then
|
||||||
|
echo " WARNING: Could not find explicit consent flow, using implicit"
|
||||||
|
AUTH_FLOW=$(ak_api "$AUTHENTIK_URL/api/v3/flows/instances/?designation=authorization" | \
|
||||||
|
python3 -c "import sys,json; r=json.load(sys.stdin)['results']; print(r[0]['pk'] if r else '')")
|
||||||
|
fi
|
||||||
|
echo " Authorization flow: $AUTH_FLOW"
|
||||||
|
|
||||||
|
PROVIDER_RESP=$(ak_api "$AUTHENTIK_URL/api/v3/providers/oauth2/" -d "{
|
||||||
|
\"name\": \"Forgejo\",
|
||||||
|
\"authorization_flow\": \"$AUTH_FLOW\",
|
||||||
|
\"client_type\": \"confidential\",
|
||||||
|
\"redirect_uris\": \"$FORGEJO_URL/user/oauth2/Authentik/callback\",
|
||||||
|
\"property_mappings\": [],
|
||||||
|
\"sub_mode\": \"hashed_user_id\",
|
||||||
|
\"include_claims_in_id_token\": true,
|
||||||
|
\"access_code_validity\": \"minutes=1\",
|
||||||
|
\"access_token_validity\": \"minutes=5\",
|
||||||
|
\"refresh_token_validity\": \"days=30\"
|
||||||
|
}" 2>/dev/null) || {
|
||||||
|
echo " Provider may already exist, checking..."
|
||||||
|
PROVIDER_RESP=$(ak_api "$AUTHENTIK_URL/api/v3/providers/oauth2/?name=Forgejo" | \
|
||||||
|
python3 -c "import sys,json; r=json.load(sys.stdin)['results']; print(json.dumps(r[0]) if r else '')")
|
||||||
|
if [ -z "$PROVIDER_RESP" ]; then echo "ERROR: Failed to create or find provider"; exit 1; fi
|
||||||
|
}
|
||||||
|
PROVIDER_PK=$(echo "$PROVIDER_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['pk'])")
|
||||||
|
CLIENT_ID=$(echo "$PROVIDER_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['client_id'])")
|
||||||
|
CLIENT_SECRET=$(echo "$PROVIDER_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('client_secret','<already-created>'))")
|
||||||
|
echo " Provider PK: $PROVIDER_PK"
|
||||||
|
echo " Client ID: $CLIENT_ID"
|
||||||
|
echo " Client Secret: $CLIENT_SECRET"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Step 3: Create Authentik Application for Forgejo ==="
|
||||||
|
APP_RESP=$(ak_api "$AUTHENTIK_URL/api/v3/core/applications/" -d "{
|
||||||
|
\"name\": \"Forgejo\",
|
||||||
|
\"slug\": \"forgejo\",
|
||||||
|
\"provider\": $PROVIDER_PK,
|
||||||
|
\"meta_launch_url\": \"$FORGEJO_URL\",
|
||||||
|
\"policy_engine_mode\": \"any\"
|
||||||
|
}" 2>/dev/null) || {
|
||||||
|
echo " Application may already exist, checking..."
|
||||||
|
APP_RESP=$(ak_api "$AUTHENTIK_URL/api/v3/core/applications/?slug=forgejo" | \
|
||||||
|
python3 -c "import sys,json; r=json.load(sys.stdin)['results']; print(json.dumps(r[0]) if r else '')")
|
||||||
|
}
|
||||||
|
APP_SLUG=$(echo "$APP_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['slug'])")
|
||||||
|
echo " Application slug: $APP_SLUG"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Step 4: Bind 'Task Submitters' group to Forgejo application ==="
|
||||||
|
# Create a policy binding that restricts access to the Task Submitters group
|
||||||
|
ak_api "$AUTHENTIK_URL/api/v3/policies/bindings/" -d "{
|
||||||
|
\"target\": \"$(echo "$APP_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['pk'])")\",
|
||||||
|
\"group\": \"$GROUP_PK\",
|
||||||
|
\"enabled\": true,
|
||||||
|
\"order\": 0,
|
||||||
|
\"negate\": false,
|
||||||
|
\"timeout\": 30
|
||||||
|
}" > /dev/null 2>&1 || echo " Binding may already exist (OK)"
|
||||||
|
echo " Group binding created"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Step 5: Add users to 'Task Submitters' group ==="
|
||||||
|
echo " Adding Viktor Barzin..."
|
||||||
|
VIKTOR_PK=$(ak_api "$AUTHENTIK_URL/api/v3/core/users/?search=vbarzin" | \
|
||||||
|
python3 -c "import sys,json; r=json.load(sys.stdin)['results']; print(r[0]['pk'] if r else '')")
|
||||||
|
if [ -n "$VIKTOR_PK" ]; then
|
||||||
|
ak_api "$AUTHENTIK_URL/api/v3/core/groups/$GROUP_PK/" -X PATCH -d "{}" > /dev/null 2>&1 || true
|
||||||
|
ak_api -X POST "$AUTHENTIK_URL/api/v3/core/groups/$GROUP_PK/add_user/" -d "{\"pk\": $VIKTOR_PK}" > /dev/null 2>&1 || true
|
||||||
|
echo " Added Viktor (PK: $VIKTOR_PK)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Step 6: Configure Forgejo OAuth2 authentication source ==="
|
||||||
|
fg_api "$FORGEJO_URL/api/v1/admin/identity-sources" -d "{
|
||||||
|
\"authentication_source\": {
|
||||||
|
\"name\": \"Authentik\",
|
||||||
|
\"type\": \"oauth2\",
|
||||||
|
\"is_active\": true,
|
||||||
|
\"is_sync_enabled\": false,
|
||||||
|
\"oauth2\": {
|
||||||
|
\"provider\": \"openidConnect\",
|
||||||
|
\"client_id\": \"$CLIENT_ID\",
|
||||||
|
\"client_secret\": \"$CLIENT_SECRET\",
|
||||||
|
\"open_id_connect_auto_discovery_url\": \"$AUTHENTIK_URL/application/o/forgejo/.well-known/openid-configuration\",
|
||||||
|
\"scopes\": [\"openid\", \"profile\", \"email\"],
|
||||||
|
\"required_claim_name\": \"\",
|
||||||
|
\"required_claim_value\": \"\",
|
||||||
|
\"group_claim_name\": \"\",
|
||||||
|
\"admin_group\": \"\",
|
||||||
|
\"restricted_group\": \"\",
|
||||||
|
\"icon_url\": \"\",
|
||||||
|
\"skip_local_2fa\": true,
|
||||||
|
\"attribute_ssn\": \"\"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}" > /dev/null 2>&1 && echo " OAuth2 source created" || {
|
||||||
|
echo " Forgejo identity-sources API may not be available."
|
||||||
|
echo " Falling back to legacy authentication-source API..."
|
||||||
|
fg_api "$FORGEJO_URL/api/v1/admin/auths" -d "{
|
||||||
|
\"name\": \"Authentik\",
|
||||||
|
\"type\": 6,
|
||||||
|
\"is_active\": true,
|
||||||
|
\"is_sync_enabled\": false,
|
||||||
|
\"cfg\": {
|
||||||
|
\"Provider\": \"openidConnect\",
|
||||||
|
\"ClientID\": \"$CLIENT_ID\",
|
||||||
|
\"ClientSecret\": \"$CLIENT_SECRET\",
|
||||||
|
\"OpenIDConnectAutoDiscoveryURL\": \"$AUTHENTIK_URL/application/o/forgejo/.well-known/openid-configuration\",
|
||||||
|
\"Scopes\": [\"openid\", \"profile\", \"email\"],
|
||||||
|
\"SkipLocalTwoFA\": true
|
||||||
|
}
|
||||||
|
}" > /dev/null 2>&1 && echo " OAuth2 source created (legacy API)" || {
|
||||||
|
echo " ERROR: Could not create OAuth2 source via API."
|
||||||
|
echo " Please create it manually in Forgejo admin panel:"
|
||||||
|
echo " 1. Go to $FORGEJO_URL/-/admin/auths/new"
|
||||||
|
echo " 2. Auth Type: OAuth2"
|
||||||
|
echo " 3. Name: Authentik"
|
||||||
|
echo " 4. OAuth2 Provider: OpenID Connect"
|
||||||
|
echo " 5. Client ID: $CLIENT_ID"
|
||||||
|
echo " 6. Client Secret: $CLIENT_SECRET"
|
||||||
|
echo " 7. Discovery URL: $AUTHENTIK_URL/application/o/forgejo/.well-known/openid-configuration"
|
||||||
|
echo " 8. Scopes: openid profile email"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Step 7: Create 'tasks' repository in Forgejo ==="
|
||||||
|
REPO_RESP=$(fg_api "$FORGEJO_URL/api/v1/user/repos" -d '{
|
||||||
|
"name": "tasks",
|
||||||
|
"description": "Task queue for OpenClaw AI agent. Create an issue to submit a task.",
|
||||||
|
"private": false,
|
||||||
|
"auto_init": true,
|
||||||
|
"default_branch": "main"
|
||||||
|
}' 2>/dev/null) && echo " Repository created" || {
|
||||||
|
echo " Repository may already exist (OK)"
|
||||||
|
REPO_RESP=$(fg_api "$FORGEJO_URL/api/v1/repos/$FORGEJO_ADMIN_USER/tasks")
|
||||||
|
}
|
||||||
|
echo " Repo: $FORGEJO_URL/$FORGEJO_ADMIN_USER/tasks"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Step 8: Disable non-issue features on tasks repo ==="
|
||||||
|
fg_api "$FORGEJO_URL/api/v1/repos/$FORGEJO_ADMIN_USER/tasks" -X PATCH -d '{
|
||||||
|
"has_pull_requests": false,
|
||||||
|
"has_wiki": false,
|
||||||
|
"has_projects": false,
|
||||||
|
"has_releases": false,
|
||||||
|
"has_packages": false,
|
||||||
|
"has_actions": false
|
||||||
|
}' > /dev/null 2>&1 && echo " Disabled PRs, wiki, projects, releases, packages, actions" || echo " Some features may not be disableable (OK)"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Step 9: Create issue labels ==="
|
||||||
|
for label_data in \
|
||||||
|
'{"name":"pending","color":"#0075ca","description":"Task waiting to be processed"}' \
|
||||||
|
'{"name":"processing","color":"#e4e669","description":"Task currently being processed by OpenClaw"}' \
|
||||||
|
'{"name":"completed","color":"#0e8a16","description":"Task completed successfully"}' \
|
||||||
|
'{"name":"failed","color":"#d73a4a","description":"Task failed during processing"}'; do
|
||||||
|
fg_api "$FORGEJO_URL/api/v1/repos/$FORGEJO_ADMIN_USER/tasks/labels" -d "$label_data" > /dev/null 2>&1 || true
|
||||||
|
done
|
||||||
|
echo " Labels created: pending, processing, completed, failed"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Step 10: Create webhook on tasks repo → n8n ==="
|
||||||
|
fg_api "$FORGEJO_URL/api/v1/repos/$FORGEJO_ADMIN_USER/tasks/hooks" -d "{
|
||||||
|
\"type\": \"gitea\",
|
||||||
|
\"config\": {
|
||||||
|
\"url\": \"$N8N_WEBHOOK_URL\",
|
||||||
|
\"content_type\": \"json\",
|
||||||
|
\"secret\": \"\"
|
||||||
|
},
|
||||||
|
\"events\": [\"issues\"],
|
||||||
|
\"active\": true
|
||||||
|
}" > /dev/null 2>&1 && echo " Webhook created → $N8N_WEBHOOK_URL" || echo " Webhook may already exist (OK)"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Setup complete!"
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo " 1. Add SOPS secrets:"
|
||||||
|
echo " forgejo_authentik_client_id = \"$CLIENT_ID\""
|
||||||
|
echo " forgejo_authentik_client_secret = \"$CLIENT_SECRET\""
|
||||||
|
echo " 2. Run: scripts/tg apply -target=module.forgejo"
|
||||||
|
echo " 3. Create n8n workflow (webhook trigger → OpenClaw exec → Forgejo comment)"
|
||||||
|
echo " 4. Add more users to 'Task Submitters' group in Authentik"
|
||||||
|
echo " 5. Test: Create an issue at $FORGEJO_URL/$FORGEJO_ADMIN_USER/tasks/issues/new"
|
||||||
|
echo "=========================================="
|
||||||
261
scripts/task-processor.sh
Executable file
261
scripts/task-processor.sh
Executable file
|
|
@ -0,0 +1,261 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Task processor for the Forgejo → OpenClaw pipeline.
|
||||||
|
# Polls Forgejo for new issues in the tasks repo, sends them to OpenClaw
|
||||||
|
# for processing, and posts results back as comments.
|
||||||
|
#
|
||||||
|
# Runs inside the OpenClaw pod via kubectl exec from a CronJob.
|
||||||
|
#
|
||||||
|
# Environment:
|
||||||
|
# FORGEJO_TOKEN — Forgejo API token with repo access
|
||||||
|
# FORGEJO_URL — Forgejo base URL (default: https://forgejo.viktorbarzin.me)
|
||||||
|
# FORGEJO_REPO — Repo in format "owner/repo" (default: vbarzin/tasks)
|
||||||
|
# OPENCLAW_URL — OpenClaw gateway URL (default: http://127.0.0.1:18789)
|
||||||
|
# OPENCLAW_TOKEN — OpenClaw gateway token
|
||||||
|
# SLACK_WEBHOOK_URL — Optional Slack webhook for notifications
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
FORGEJO_URL="${FORGEJO_URL:-https://forgejo.viktorbarzin.me}"
|
||||||
|
FORGEJO_REPO="${FORGEJO_REPO:-viktor/tasks}"
|
||||||
|
OPENCLAW_URL="${OPENCLAW_URL:-https://integrate.api.nvidia.com}"
|
||||||
|
SLACK_WEBHOOK_URL="${SLACK_WEBHOOK_URL:-}"
|
||||||
|
|
||||||
|
: "${FORGEJO_TOKEN:?FORGEJO_TOKEN is required}"
|
||||||
|
: "${OPENCLAW_TOKEN:?OPENCLAW_TOKEN is required}"
|
||||||
|
FORGEJO_BOT_USER="${FORGEJO_BOT_USER:-viktor}"
|
||||||
|
|
||||||
|
fg_api() {
|
||||||
|
curl -sf -H "Authorization: token $FORGEJO_TOKEN" -H "Content-Type: application/json" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_label_id() {
|
||||||
|
local label_name="$1"
|
||||||
|
fg_api "$FORGEJO_URL/api/v1/repos/$FORGEJO_REPO/labels?limit=50" | \
|
||||||
|
python3 -c "
|
||||||
|
import sys, json
|
||||||
|
labels = json.load(sys.stdin)
|
||||||
|
name = sys.argv[1]
|
||||||
|
for l in labels:
|
||||||
|
if l['name'] == name:
|
||||||
|
print(l['id'])
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print(0)
|
||||||
|
" "$label_name"
|
||||||
|
}
|
||||||
|
|
||||||
|
add_label() {
|
||||||
|
local issue_id="$1" label_name="$2"
|
||||||
|
local label_id
|
||||||
|
label_id=$(get_label_id "$label_name")
|
||||||
|
if [ "$label_id" != "0" ]; then
|
||||||
|
fg_api "$FORGEJO_URL/api/v1/repos/$FORGEJO_REPO/issues/$issue_id/labels" \
|
||||||
|
-d "{\"labels\":[$label_id]}" > /dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_label() {
|
||||||
|
local issue_id="$1" label_name="$2"
|
||||||
|
local label_id
|
||||||
|
label_id=$(get_label_id "$label_name")
|
||||||
|
if [ "$label_id" != "0" ]; then
|
||||||
|
fg_api -X DELETE "$FORGEJO_URL/api/v1/repos/$FORGEJO_REPO/issues/$issue_id/labels/$label_id" > /dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
post_comment() {
|
||||||
|
local issue_id="$1"
|
||||||
|
# Read comment body from stdin to avoid quoting issues
|
||||||
|
python3 -c "
|
||||||
|
import sys, json
|
||||||
|
body = sys.stdin.read()
|
||||||
|
print(json.dumps({'body': body}))
|
||||||
|
" | fg_api "$FORGEJO_URL/api/v1/repos/$FORGEJO_REPO/issues/$issue_id/comments" -d @- > /dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
close_issue() {
|
||||||
|
local issue_id="$1"
|
||||||
|
fg_api "$FORGEJO_URL/api/v1/repos/$FORGEJO_REPO/issues/$issue_id" \
|
||||||
|
-X PATCH -d '{"state": "closed"}' > /dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
get_comment_history() {
|
||||||
|
local issue_id="$1"
|
||||||
|
fg_api "$FORGEJO_URL/api/v1/repos/$FORGEJO_REPO/issues/$issue_id/comments?limit=20" 2>/dev/null | \
|
||||||
|
python3 -c "
|
||||||
|
import sys, json
|
||||||
|
bot_user = sys.argv[1]
|
||||||
|
comments = json.load(sys.stdin)
|
||||||
|
history = []
|
||||||
|
for c in comments:
|
||||||
|
user = c.get('user', {}).get('login', 'unknown')
|
||||||
|
body = c.get('body', '')
|
||||||
|
# Skip bot's own comments to keep context clean
|
||||||
|
if user == bot_user:
|
||||||
|
# Include a short summary of previous responses
|
||||||
|
if '## OpenClaw Task Result' in body:
|
||||||
|
# Extract just the result content (skip header/footer)
|
||||||
|
lines = body.split('\n')
|
||||||
|
content = [l for l in lines if not l.startswith('## ') and not l.startswith('---') and not l.startswith('*Processed')]
|
||||||
|
summary = '\n'.join(content).strip()[:500]
|
||||||
|
if summary:
|
||||||
|
history.append(f'[Previous AI response]: {summary}')
|
||||||
|
else:
|
||||||
|
history.append(f'[{user}]: {body}')
|
||||||
|
print('\n\n'.join(history))
|
||||||
|
" "$FORGEJO_BOT_USER" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
notify_slack() {
|
||||||
|
if [ -n "$SLACK_WEBHOOK_URL" ]; then
|
||||||
|
python3 -c "
|
||||||
|
import json, sys
|
||||||
|
print(json.dumps({'text': sys.argv[1]}))
|
||||||
|
" "$1" | curl -sf -X POST "$SLACK_WEBHOOK_URL" \
|
||||||
|
-H "Content-Type: application/json" -d @- > /dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
process_issue() {
|
||||||
|
local issue_id="$1" title="$2" body="$3" author="$4"
|
||||||
|
|
||||||
|
echo "Processing issue #$issue_id: $title (by $author)"
|
||||||
|
|
||||||
|
# Mark as processing
|
||||||
|
add_label "$issue_id" "processing"
|
||||||
|
remove_label "$issue_id" "pending"
|
||||||
|
remove_label "$issue_id" "completed"
|
||||||
|
|
||||||
|
# Fetch comment history for context
|
||||||
|
local comment_history
|
||||||
|
comment_history=$(get_comment_history "$issue_id")
|
||||||
|
|
||||||
|
# Call OpenClaw gateway API (OpenAI-compatible chat completions)
|
||||||
|
# Use python to safely build the JSON payload
|
||||||
|
local response
|
||||||
|
response=$(python3 -c "
|
||||||
|
import json, sys
|
||||||
|
title = sys.argv[1]
|
||||||
|
body = sys.argv[2]
|
||||||
|
author = sys.argv[3]
|
||||||
|
comment_history = sys.argv[4]
|
||||||
|
|
||||||
|
prompt = f'''You are processing a task submitted by {author} via the Forgejo task queue.
|
||||||
|
|
||||||
|
Task title: {title}
|
||||||
|
|
||||||
|
Task description:
|
||||||
|
{body}'''
|
||||||
|
|
||||||
|
if comment_history.strip():
|
||||||
|
prompt += f'''
|
||||||
|
|
||||||
|
Conversation history (follow-up comments):
|
||||||
|
{comment_history}
|
||||||
|
|
||||||
|
The latest comment is the most recent request. Address it in context of the original task and prior conversation.'''
|
||||||
|
|
||||||
|
prompt += '''
|
||||||
|
|
||||||
|
Please execute this task. When done, provide a clear summary of what was done and any results.
|
||||||
|
If the task requires infrastructure changes, describe what changes would be needed but do NOT apply them automatically — list the commands/changes for review.'''
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
'model': 'mistralai/mistral-large-3-675b-instruct-2512',
|
||||||
|
'messages': [
|
||||||
|
{'role': 'system', 'content': 'You are an infrastructure AI assistant. Process the task and provide actionable results. Be concise.'},
|
||||||
|
{'role': 'user', 'content': prompt}
|
||||||
|
],
|
||||||
|
'max_tokens': 8192,
|
||||||
|
'temperature': 0.3
|
||||||
|
}
|
||||||
|
print(json.dumps(payload))
|
||||||
|
" "$title" "$body" "$author" "$comment_history" | \
|
||||||
|
curl -sf --max-time 300 \
|
||||||
|
-H "Authorization: Bearer $OPENCLAW_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"$OPENCLAW_URL/v1/chat/completions" \
|
||||||
|
-d @- 2>&1) || {
|
||||||
|
echo " ERROR: OpenClaw API call failed"
|
||||||
|
echo "Failed to process this task. OpenClaw API returned an error. Please check the CronJob logs or process manually." | \
|
||||||
|
post_comment "$issue_id"
|
||||||
|
add_label "$issue_id" "failed"
|
||||||
|
remove_label "$issue_id" "processing"
|
||||||
|
notify_slack ":x: Task #$issue_id failed: $title"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract the response content and post as comment
|
||||||
|
python3 -c "
|
||||||
|
import sys, json
|
||||||
|
try:
|
||||||
|
data = json.load(sys.stdin)
|
||||||
|
msg = data['choices'][0]['message']
|
||||||
|
# Some models put content in reasoning_content instead of content
|
||||||
|
result = msg.get('content') or msg.get('reasoning_content') or msg.get('reasoning') or 'No response generated.'
|
||||||
|
except Exception as e:
|
||||||
|
result = f'Error parsing OpenClaw response: {e}'
|
||||||
|
|
||||||
|
body = f'## OpenClaw Task Result\n\n{result}\n\n---\n*Processed automatically by the OpenClaw task pipeline.*'
|
||||||
|
print(body)
|
||||||
|
" <<< "$response" | post_comment "$issue_id"
|
||||||
|
|
||||||
|
# Update labels and close
|
||||||
|
add_label "$issue_id" "completed"
|
||||||
|
remove_label "$issue_id" "processing"
|
||||||
|
close_issue "$issue_id"
|
||||||
|
|
||||||
|
echo " Issue #$issue_id processed and closed"
|
||||||
|
notify_slack ":white_check_mark: Task #$issue_id completed: $title"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Main ---
|
||||||
|
|
||||||
|
echo "=== Task Processor $(date -u +%Y-%m-%dT%H:%M:%SZ) ==="
|
||||||
|
|
||||||
|
# List open issues
|
||||||
|
ISSUES=$(fg_api "$FORGEJO_URL/api/v1/repos/$FORGEJO_REPO/issues?state=open&type=issues&limit=10&sort=created&direction=asc" 2>/dev/null) || {
|
||||||
|
echo "ERROR: Could not fetch issues from Forgejo"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse pending issues into a temp file (avoids delimiter issues)
|
||||||
|
PENDING_FILE=$(mktemp)
|
||||||
|
trap 'rm -f "$PENDING_FILE"' EXIT
|
||||||
|
|
||||||
|
python3 -c "
|
||||||
|
import sys, json
|
||||||
|
issues = json.load(sys.stdin)
|
||||||
|
for issue in issues:
|
||||||
|
labels = [l['name'] for l in issue.get('labels', [])]
|
||||||
|
# Process if: no processing label AND (no completed label OR issue was reopened)
|
||||||
|
if 'processing' not in labels:
|
||||||
|
# Write each issue as a JSON line
|
||||||
|
print(json.dumps({
|
||||||
|
'id': issue['number'],
|
||||||
|
'title': issue['title'],
|
||||||
|
'body': (issue.get('body') or '')[:4000],
|
||||||
|
'author': issue['user']['login']
|
||||||
|
}))
|
||||||
|
" <<< "$ISSUES" > "$PENDING_FILE"
|
||||||
|
|
||||||
|
ISSUE_COUNT=$(wc -l < "$PENDING_FILE" | tr -d ' ')
|
||||||
|
|
||||||
|
if [ "$ISSUE_COUNT" = "0" ]; then
|
||||||
|
echo "No pending issues to process"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Found $ISSUE_COUNT pending issue(s)"
|
||||||
|
|
||||||
|
# Process each pending issue (one JSON object per line)
|
||||||
|
while IFS= read -r line; do
|
||||||
|
issue_id=$(python3 -c "import json,sys; print(json.loads(sys.argv[1])['id'])" "$line")
|
||||||
|
title=$(python3 -c "import json,sys; print(json.loads(sys.argv[1])['title'])" "$line")
|
||||||
|
body=$(python3 -c "import json,sys; print(json.loads(sys.argv[1])['body'])" "$line")
|
||||||
|
author=$(python3 -c "import json,sys; print(json.loads(sys.argv[1])['author'])" "$line")
|
||||||
|
process_issue "$issue_id" "$title" "$body" "$author" || true
|
||||||
|
done < "$PENDING_FILE"
|
||||||
|
|
||||||
|
echo "=== Task processing complete ==="
|
||||||
|
|
@ -3,6 +3,11 @@ variable "tls_secret_name" {
|
||||||
sensitive = true
|
sensitive = true
|
||||||
}
|
}
|
||||||
variable "nfs_server" { type = string }
|
variable "nfs_server" { type = string }
|
||||||
|
variable "forgejo_authentik_client_id" { type = string }
|
||||||
|
variable "forgejo_authentik_client_secret" {
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
resource "kubernetes_namespace" "forgejo" {
|
resource "kubernetes_namespace" "forgejo" {
|
||||||
|
|
@ -66,6 +71,29 @@ resource "kubernetes_deployment" "forgejo" {
|
||||||
name = "USER_GID"
|
name = "USER_GID"
|
||||||
value = 1000
|
value = 1000
|
||||||
}
|
}
|
||||||
|
# Root URL for OAuth2 redirect callbacks
|
||||||
|
env {
|
||||||
|
name = "FORGEJO__server__ROOT_URL"
|
||||||
|
value = "https://forgejo.viktorbarzin.me"
|
||||||
|
}
|
||||||
|
# Disable local registration — only allow OAuth2 (Authentik)
|
||||||
|
env {
|
||||||
|
name = "FORGEJO__service__DISABLE_REGISTRATION"
|
||||||
|
value = "false"
|
||||||
|
}
|
||||||
|
env {
|
||||||
|
name = "FORGEJO__service__ALLOW_ONLY_EXTERNAL_REGISTRATION"
|
||||||
|
value = "true"
|
||||||
|
}
|
||||||
|
env {
|
||||||
|
name = "FORGEJO__openid__ENABLE_OPENID_SIGNIN"
|
||||||
|
value = "false"
|
||||||
|
}
|
||||||
|
# Allow webhook delivery to internal k8s services
|
||||||
|
env {
|
||||||
|
name = "FORGEJO__webhook__ALLOWED_HOST_LIST"
|
||||||
|
value = "*.svc.cluster.local"
|
||||||
|
}
|
||||||
volume_mount {
|
volume_mount {
|
||||||
name = "data"
|
name = "data"
|
||||||
mount_path = "/data"
|
mount_path = "/data"
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,49 @@ module "nfs_data" {
|
||||||
nfs_path = "/mnt/main/n8n"
|
nfs_path = "/mnt/main/n8n"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# --- RBAC: Allow n8n to exec into OpenClaw pods for task execution ---
|
||||||
|
|
||||||
|
resource "kubernetes_service_account" "n8n" {
|
||||||
|
metadata {
|
||||||
|
name = "n8n"
|
||||||
|
namespace = kubernetes_namespace.n8n.metadata[0].name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "kubernetes_role" "n8n_openclaw_exec" {
|
||||||
|
metadata {
|
||||||
|
name = "n8n-openclaw-exec"
|
||||||
|
namespace = "openclaw"
|
||||||
|
}
|
||||||
|
rule {
|
||||||
|
api_groups = [""]
|
||||||
|
resources = ["pods"]
|
||||||
|
verbs = ["get", "list"]
|
||||||
|
}
|
||||||
|
rule {
|
||||||
|
api_groups = [""]
|
||||||
|
resources = ["pods/exec"]
|
||||||
|
verbs = ["create"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "kubernetes_role_binding" "n8n_openclaw_exec" {
|
||||||
|
metadata {
|
||||||
|
name = "n8n-openclaw-exec"
|
||||||
|
namespace = "openclaw"
|
||||||
|
}
|
||||||
|
subject {
|
||||||
|
kind = "ServiceAccount"
|
||||||
|
name = kubernetes_service_account.n8n.metadata[0].name
|
||||||
|
namespace = kubernetes_namespace.n8n.metadata[0].name
|
||||||
|
}
|
||||||
|
role_ref {
|
||||||
|
api_group = "rbac.authorization.k8s.io"
|
||||||
|
kind = "Role"
|
||||||
|
name = kubernetes_role.n8n_openclaw_exec.metadata[0].name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resource "kubernetes_deployment" "n8n" {
|
resource "kubernetes_deployment" "n8n" {
|
||||||
metadata {
|
metadata {
|
||||||
name = "n8n"
|
name = "n8n"
|
||||||
|
|
@ -56,6 +99,7 @@ resource "kubernetes_deployment" "n8n" {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
spec {
|
spec {
|
||||||
|
service_account_name = kubernetes_service_account.n8n.metadata[0].name
|
||||||
container {
|
container {
|
||||||
name = "n8n"
|
name = "n8n"
|
||||||
image = "docker.n8n.io/n8nio/n8n"
|
image = "docker.n8n.io/n8nio/n8n"
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,10 @@ variable "openclaw_telegram_bot_token" {
|
||||||
type = string
|
type = string
|
||||||
sensitive = true
|
sensitive = true
|
||||||
}
|
}
|
||||||
|
variable "forgejo_api_token" {
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
variable "nfs_server" { type = string }
|
variable "nfs_server" { type = string }
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -660,6 +664,199 @@ module "ingress" {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# --- Webhook receiver: triggers task-processor Job on Forgejo issue events ---
|
||||||
|
|
||||||
|
resource "kubernetes_config_map" "task_webhook" {
|
||||||
|
metadata {
|
||||||
|
name = "task-webhook"
|
||||||
|
namespace = kubernetes_namespace.openclaw.metadata[0].name
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
"server.py" = <<-PYEOF
|
||||||
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||||
|
import subprocess, time, json, os
|
||||||
|
|
||||||
|
BOT_USER = os.environ.get('FORGEJO_BOT_USER', 'viktor')
|
||||||
|
|
||||||
|
class Handler(BaseHTTPRequestHandler):
|
||||||
|
def do_POST(self):
|
||||||
|
try:
|
||||||
|
body = self.rfile.read(int(self.headers.get('Content-Length', 0)))
|
||||||
|
data = json.loads(body)
|
||||||
|
action = data.get('action', '')
|
||||||
|
|
||||||
|
# Trigger on: new issue, reopened issue, or new comment
|
||||||
|
trigger = False
|
||||||
|
if action in ('opened', 'reopened'):
|
||||||
|
issue = data.get('issue', {})
|
||||||
|
print(f"Issue #{issue.get('number','?')} {action}: {issue.get('title','?')}")
|
||||||
|
trigger = True
|
||||||
|
elif action == 'created' and 'comment' in data:
|
||||||
|
comment = data.get('comment', {})
|
||||||
|
commenter = comment.get('user', {}).get('login', '')
|
||||||
|
# Skip comments from the bot itself to avoid loops
|
||||||
|
if commenter != BOT_USER:
|
||||||
|
issue = data.get('issue', {})
|
||||||
|
print(f"Comment on #{issue.get('number','?')} by {commenter}")
|
||||||
|
trigger = True
|
||||||
|
else:
|
||||||
|
print(f"Skipping own comment on #{data.get('issue',{}).get('number','?')}")
|
||||||
|
|
||||||
|
if trigger:
|
||||||
|
job_name = f"task-processor-{int(time.time())}"
|
||||||
|
subprocess.run([
|
||||||
|
'kubectl', 'create', 'job', job_name,
|
||||||
|
'--from=cronjob/task-processor',
|
||||||
|
'-n', 'openclaw'
|
||||||
|
], check=True)
|
||||||
|
self.send_response(200)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(b'{"ok":true}')
|
||||||
|
else:
|
||||||
|
self.send_response(200)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(b'{"ok":true,"skipped":true}')
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {e}")
|
||||||
|
self.send_response(500)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(f'{{"error":"{e}"}}'.encode())
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
self.send_response(200)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(b'{"status":"ok"}')
|
||||||
|
|
||||||
|
def log_message(self, fmt, *args):
|
||||||
|
print(f"[webhook] {args[0]} {args[1]} {args[2]}")
|
||||||
|
|
||||||
|
print("Task webhook receiver listening on :8080")
|
||||||
|
HTTPServer(('', 8080), Handler).serve_forever()
|
||||||
|
PYEOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "kubernetes_service_account" "task_webhook" {
|
||||||
|
metadata {
|
||||||
|
name = "task-webhook"
|
||||||
|
namespace = kubernetes_namespace.openclaw.metadata[0].name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "kubernetes_role" "task_webhook" {
|
||||||
|
metadata {
|
||||||
|
name = "task-webhook-job-creator"
|
||||||
|
namespace = kubernetes_namespace.openclaw.metadata[0].name
|
||||||
|
}
|
||||||
|
rule {
|
||||||
|
api_groups = ["batch"]
|
||||||
|
resources = ["jobs", "cronjobs"]
|
||||||
|
verbs = ["get", "list", "create"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "kubernetes_role_binding" "task_webhook" {
|
||||||
|
metadata {
|
||||||
|
name = "task-webhook-job-creator"
|
||||||
|
namespace = kubernetes_namespace.openclaw.metadata[0].name
|
||||||
|
}
|
||||||
|
subject {
|
||||||
|
kind = "ServiceAccount"
|
||||||
|
name = kubernetes_service_account.task_webhook.metadata[0].name
|
||||||
|
namespace = kubernetes_namespace.openclaw.metadata[0].name
|
||||||
|
}
|
||||||
|
role_ref {
|
||||||
|
api_group = "rbac.authorization.k8s.io"
|
||||||
|
kind = "Role"
|
||||||
|
name = kubernetes_role.task_webhook.metadata[0].name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "kubernetes_deployment" "task_webhook" {
|
||||||
|
metadata {
|
||||||
|
name = "task-webhook"
|
||||||
|
namespace = kubernetes_namespace.openclaw.metadata[0].name
|
||||||
|
labels = {
|
||||||
|
app = "task-webhook"
|
||||||
|
tier = local.tiers.aux
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spec {
|
||||||
|
replicas = 1
|
||||||
|
selector {
|
||||||
|
match_labels = {
|
||||||
|
app = "task-webhook"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
template {
|
||||||
|
metadata {
|
||||||
|
labels = {
|
||||||
|
app = "task-webhook"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spec {
|
||||||
|
service_account_name = kubernetes_service_account.task_webhook.metadata[0].name
|
||||||
|
container {
|
||||||
|
name = "webhook"
|
||||||
|
image = "python:3-alpine"
|
||||||
|
command = ["sh", "-c", "apk add --no-cache curl > /dev/null 2>&1 && curl -sfL https://dl.k8s.io/release/v1.34.2/bin/linux/amd64/kubectl -o /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl && exec python3 -u /app/server.py"]
|
||||||
|
port {
|
||||||
|
container_port = 8080
|
||||||
|
}
|
||||||
|
volume_mount {
|
||||||
|
name = "app"
|
||||||
|
mount_path = "/app"
|
||||||
|
}
|
||||||
|
resources {
|
||||||
|
requests = {
|
||||||
|
cpu = "5m"
|
||||||
|
memory = "32Mi"
|
||||||
|
}
|
||||||
|
limits = {
|
||||||
|
cpu = "100m"
|
||||||
|
memory = "64Mi"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
volume {
|
||||||
|
name = "app"
|
||||||
|
config_map {
|
||||||
|
name = kubernetes_config_map.task_webhook.metadata[0].name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "kubernetes_service" "task_webhook" {
|
||||||
|
metadata {
|
||||||
|
name = "task-webhook"
|
||||||
|
namespace = kubernetes_namespace.openclaw.metadata[0].name
|
||||||
|
labels = {
|
||||||
|
app = "task-webhook"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spec {
|
||||||
|
selector = {
|
||||||
|
app = "task-webhook"
|
||||||
|
}
|
||||||
|
port {
|
||||||
|
port = 80
|
||||||
|
target_port = 8080
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module "task_webhook_ingress" {
|
||||||
|
source = "../../modules/kubernetes/ingress_factory"
|
||||||
|
namespace = kubernetes_namespace.openclaw.metadata[0].name
|
||||||
|
name = "task-webhook"
|
||||||
|
tls_secret_name = var.tls_secret_name
|
||||||
|
host = "task-webhook"
|
||||||
|
port = 80
|
||||||
|
}
|
||||||
|
|
||||||
# --- CronJob: Scheduled cluster health check ---
|
# --- CronJob: Scheduled cluster health check ---
|
||||||
|
|
||||||
resource "kubernetes_service_account" "healthcheck" {
|
resource "kubernetes_service_account" "healthcheck" {
|
||||||
|
|
@ -768,3 +965,84 @@ resource "kubernetes_cron_job_v1" "cluster_healthcheck" {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# --- CronJob: Task processor — polls Forgejo issues and triggers OpenClaw ---
|
||||||
|
|
||||||
|
resource "kubernetes_cron_job_v1" "task_processor" {
|
||||||
|
metadata {
|
||||||
|
name = "task-processor"
|
||||||
|
namespace = kubernetes_namespace.openclaw.metadata[0].name
|
||||||
|
labels = {
|
||||||
|
app = "task-processor"
|
||||||
|
tier = local.tiers.aux
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spec {
|
||||||
|
schedule = "*/5 * * * *"
|
||||||
|
concurrency_policy = "Forbid"
|
||||||
|
failed_jobs_history_limit = 3
|
||||||
|
successful_jobs_history_limit = 3
|
||||||
|
|
||||||
|
job_template {
|
||||||
|
metadata {
|
||||||
|
labels = {
|
||||||
|
app = "task-processor"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spec {
|
||||||
|
active_deadline_seconds = 600
|
||||||
|
backoff_limit = 0
|
||||||
|
template {
|
||||||
|
metadata {
|
||||||
|
labels = {
|
||||||
|
app = "task-processor"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spec {
|
||||||
|
service_account_name = kubernetes_service_account.healthcheck.metadata[0].name
|
||||||
|
restart_policy = "Never"
|
||||||
|
|
||||||
|
container {
|
||||||
|
name = "task-processor"
|
||||||
|
image = "bitnami/kubectl:latest"
|
||||||
|
command = ["bash", "-c", <<-EOF
|
||||||
|
# Find the openclaw pod
|
||||||
|
POD=$(kubectl get pods -n openclaw -l app=openclaw -o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
|
||||||
|
if [ -z "$POD" ]; then
|
||||||
|
echo "ERROR: OpenClaw pod not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Executing task processor in pod $POD..."
|
||||||
|
kubectl exec -n openclaw "$POD" -c openclaw -- \
|
||||||
|
env FORGEJO_TOKEN="$FORGEJO_TOKEN" \
|
||||||
|
OPENCLAW_TOKEN="$OPENCLAW_TOKEN" \
|
||||||
|
OPENCLAW_URL="https://integrate.api.nvidia.com" \
|
||||||
|
bash /workspace/infra/scripts/task-processor.sh
|
||||||
|
EOF
|
||||||
|
]
|
||||||
|
|
||||||
|
env {
|
||||||
|
name = "FORGEJO_TOKEN"
|
||||||
|
value = var.forgejo_api_token
|
||||||
|
}
|
||||||
|
env {
|
||||||
|
name = "OPENCLAW_TOKEN"
|
||||||
|
value = var.nvidia_api_key
|
||||||
|
}
|
||||||
|
|
||||||
|
resources {
|
||||||
|
requests = {
|
||||||
|
cpu = "50m"
|
||||||
|
memory = "64Mi"
|
||||||
|
}
|
||||||
|
limits = {
|
||||||
|
memory = "128Mi"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue