# Gogs Webhook Server A modular Node.js server for receiving and processing Gogs webhooks. The server logs all incoming webhooks to the console, executes configured commands based on webhook events, and provides an extensible callback system for custom event handling. ## Features - **Persistent Job Queue**: FIFO queue ensures sequential execution and crash recovery - **Modular Architecture**: Clean separation of concerns (server, handler, queue, logger) - **Command Execution**: Execute shell or Node.js commands on webhook events - **JSON Configuration**: Flexible command configuration via `commands.json` - **Multiple Commands per Event**: Run multiple commands for each webhook event type - **Variable Substitution**: Access repo name, branch, pusher, issue details, and more in commands - **Branch Filtering**: Execute commands only for specific branches - **Node.js & Shell Support**: Run both shell scripts and Node.js scripts - **Per-Command Settings**: Configure working directory and timeout per command - **Extensible Callback System**: Register callbacks for specific events or all events - **Console Logging**: Colored, formatted logging of all webhook events - **Health Check Endpoint**: Built-in health monitoring - **Graceful Shutdown**: Saves queue state and ensures proper cleanup on process termination - **Zero Dependencies**: Pure Node.js implementation ## Requirements - Node.js >= 18.0.0 ## Installation ```bash npm install ``` ## Usage ### Quick Start 1. Copy the example files: ```bash cp .env.example .env cp commands.json.example commands.json ``` 2. Edit `.env` to enable/disable webhook events 3. Edit `commands.json` to configure commands for webhook events (see Configuration section below) 4. Start the server: ```bash npm start ``` The server will start on port 3000 and execute configured commands when webhooks are received. ### Development Mode (Auto-reload) ```bash npm run dev ``` ### Running as a Systemd Service For production deployments, you can run the server as a systemd service. This provides: - Automatic start on system boot - Automatic restart on failure - Centralized logging via journalctl - Running as an unprivileged user See [SYSTEMD.md](./SYSTEMD.md) for detailed installation and configuration instructions. Quick example: ```bash # Install the service sudo cp agent-manager.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable agent-manager sudo systemctl start agent-manager # View logs journalctl -u agent-manager -f -n100 ``` ### Configuration The server uses two configuration files: 1. `.env` - Enable/disable webhook events and server settings 2. `commands.json` - Define commands to execute for each event type #### Basic Server Configuration (`.env`) ```env HOST=0.0.0.0 PORT=3000 WEBHOOK_PATH=/webhook WEBHOOK_SECRET=your-secret-here ``` - `HOST` - The network interface to bind to (default: `0.0.0.0` for all interfaces, use `127.0.0.1` for localhost only) - `PORT` - The port number to listen on (default: `3000`) - `WEBHOOK_PATH` - The URL path for webhooks (default: `/webhook`) - `WEBHOOK_SECRET` - Optional secret for webhook signature verification (see Security section below) #### Enable/Disable Events (`.env`) ```env WEBHOOK_PUSH_ENABLED=true WEBHOOK_PULL_REQUEST_ENABLED=false WEBHOOK_CREATE_ENABLED=false WEBHOOK_DELETE_ENABLED=false WEBHOOK_RELEASE_ENABLED=false WEBHOOK_ISSUES_ENABLED=false WEBHOOK_ISSUE_COMMENT_ENABLED=false WEBHOOK_GLOBAL_ENABLED=false ``` #### Command Configuration (`commands.json`) Commands are configured in a JSON file with the following structure: ```json { "commands": { "push": [ { "name": "deploy-production", "description": "Deploy to production on main branch", "type": "shell", "command": "/path/to/deploy.sh", "args": ["{{branch}}", "{{repo}}", "{{pusher}}"], "cwd": "/workspace", "timeout": 600000, "filterBranch": "main" } ], "issues": [ { "name": "process-issue", "description": "Process issue with Node.js script", "type": "node", "command": "./scripts/process-issue.js", "args": ["--repo", "{{repo}}", "--issue", "{{issue_number}}"], "cwd": null, "timeout": 300000, "filterBranch": null } ] } } ``` **Command Fields:** - `name` - Unique identifier for the command - `description` - Human-readable description - `type` - Either `"shell"` (bash script) or `"node"` (Node.js script) - `command` - The command or script to execute - `args` - Array of arguments (supports variable substitution) - `cwd` - Working directory (null uses project root) - `timeout` - Timeout in milliseconds - `filterBranch` - Only run for specific branch (null = all branches) **Available Variables:** - `{{repo}}` - Repository name (e.g., "my-repo") - `{{full_repo}}` - Full repository name (e.g., "user/my-repo") - `{{repo_owner}}` - Repository owner username (e.g., "user") - `{{branch}}` - Branch name (e.g., "main", "feature/new-feature") - `{{pusher}}` - Username of person who triggered the event - `{{event}}` - Event type (e.g., "push", "pull_request") - `{{commit}}` - Latest commit hash (short, for push events) - `{{commit_msg}}` - Latest commit message (for push events) - `{{pr_number}}` - Pull request number (for PR events) - `{{pr_action}}` - Pull request action (opened, closed, etc.) - `{{tag}}` - Tag name (for create/delete tag events) - `{{ref_type}}` - "branch" or "tag" - `{{issue_number}}` - Issue number (for issue events) - `{{issue_title}}` - Issue title (for issue events) - `{{issue_action}}` - Issue action (opened, closed, reopened, etc.) - `{{issue_body}}` - Issue description/body (for issue events) - `{{issue_assignee}}` - Username of the person assigned to the issue (for issue events) - `{{comment_body}}` - Comment text (for issue_comment events) **Supported Event Types:** - `push` - Push events - `pull_request` - Pull request events - `create` - Branch/tag creation - `delete` - Branch/tag deletion - `release` - Release events - `issues` - Issue events (opened, closed, reopened, etc.) - `issue_comment` - Issue comment events - `global` - All events ### Endpoints - `POST /webhook` - Webhook endpoint - `GET /health` - Health check endpoint ## Security ### Webhook Signature Verification The server supports HMAC-SHA256 signature verification to ensure webhooks are authentic and haven't been tampered with. #### How It Works When you configure a secret in Gogs webhook settings and in your `.env` file, Gogs will sign each webhook request with an HMAC-SHA256 signature and send it in the `X-Gogs-Signature` header. The server verifies this signature before processing the webhook. #### Setup 1. **Configure Secret in .env**: ```env WEBHOOK_SECRET=your-secure-random-string ``` 2. **Configure Secret in Gogs**: - Go to your repository settings → Webhooks - Edit or create a webhook - Set the same secret value in the "Secret" field - Set Content Type to `application/json` #### Behavior - **Secret Configured**: The server requires a valid `X-Gogs-Signature` header and rejects webhooks with missing or invalid signatures (HTTP 401) - **No Secret Configured**: Signature verification is skipped, and all webhooks are accepted (useful for development) #### Security Best Practices - Use a strong, random secret (at least 32 characters) - Keep your secret secure and never commit it to version control - Use different secrets for different environments (development, staging, production) - Rotate secrets periodically for enhanced security #### Verification Process The server: 1. Computes an HMAC-SHA256 hash of the raw request body using the configured secret 2. Compares it with the signature from the `X-Gogs-Signature` header using a timing-safe comparison 3. Accepts the webhook if signatures match, rejects otherwise This ensures: - **Authenticity**: The webhook came from Gogs with the correct secret - **Integrity**: The payload hasn't been modified in transit - **Replay Protection**: Combined with Gogs' delivery ID tracking ## Job Queue System The webhook server uses a persistent job queue to ensure reliable and sequential command execution: ### How It Works 1. **Webhook Received**: When a webhook arrives, it's immediately accepted and added to the queue 2. **Quick Response**: The server responds to Gogs instantly (no timeout issues) 3. **Sequential Processing**: Jobs are executed one at a time in FIFO (first-in-first-out) order 4. **Persistence**: Queue state is saved to `queue.json` after every change 5. **Crash Recovery**: If the server crashes or restarts, pending jobs are automatically resumed ### Benefits - **No Lost Webhooks**: Even if the server crashes, queued jobs are preserved and executed after restart - **No Concurrent Execution**: Prevents conflicts when multiple webhooks trigger deployment scripts - **Handle Bursts**: Can accept many webhooks quickly while processing them sequentially - **Reliable**: Long-running commands won't block new webhook deliveries ### Queue File The queue state is stored in `queue.json` in the project root: ```json { "queue": [ { "id": 1, "eventType": "push", "status": "pending", "createdAt": "2025-10-28T12:00:00.000Z" } ], "jobIdCounter": 5, "lastSaved": "2025-10-28T12:05:00.000Z" } ``` This file is automatically managed and should not be manually edited. It's excluded from git via `.gitignore`. ## Real-World Examples ### Example 1: Auto-deploy on push to main **.env:** ```env WEBHOOK_PUSH_ENABLED=true ``` **commands.json:** ```json { "commands": { "push": [ { "name": "deploy-production", "description": "Deploy to production on main branch", "type": "shell", "command": "/home/deploy/deploy.sh", "args": ["{{repo}}", "{{branch}}", "{{commit}}"], "cwd": null, "timeout": 600000, "filterBranch": "main" } ] } } ``` **deploy.sh:** ```bash #!/bin/bash REPO=$1 BRANCH=$2 COMMIT=$3 echo "Deploying $REPO from $BRANCH (commit: $COMMIT)" cd /var/www/$REPO git pull origin $BRANCH npm install npm run build systemctl restart $REPO ``` ### Example 2: Send notification on any webhook **.env:** ```env WEBHOOK_GLOBAL_ENABLED=true ``` **commands.json:** ```json { "commands": { "global": [ { "name": "slack-notification", "description": "Send Slack notification for all events", "type": "shell", "command": "curl", "args": ["-X", "POST", "https://api.slack.com/webhook", "-d", "{\"text\":\"{{event}} in {{repo}} by {{pusher}}\"}"], "cwd": null, "timeout": 30000, "filterBranch": null } ] } } ``` ### Example 3: Run tests on pull request with Node.js **.env:** ```env WEBHOOK_PULL_REQUEST_ENABLED=true ``` **commands.json:** ```json { "commands": { "pull_request": [ { "name": "run-tests", "description": "Run automated tests on pull request", "type": "node", "command": "./scripts/run-tests.js", "args": ["--repo", "{{repo}}", "--pr", "{{pr_number}}", "--author", "{{pusher}}"], "cwd": "/home/ci", "timeout": 900000, "filterBranch": null } ] } } ``` ## Extending with Callbacks The server is designed to be easily extended with custom callbacks. See `examples/with-callbacks.js` for a complete example. ### Register Event-Specific Callbacks ```javascript import { WebhookServer } from './src/server.js'; const server = new WebhookServer({ port: 3000 }); const handler = server.getHandler(); // Handle push events handler.on('push', async (payload, headers) => { console.log('Push to:', payload.repository.name); console.log('Commits:', payload.commits.length); // Your custom logic here }); // Handle pull request events handler.on('pull_request', async (payload, headers) => { console.log('PR:', payload.action); // Your custom logic here }); server.start(); ``` ### Register Global Callbacks ```javascript // Handle all events handler.onAny(async (eventType, payload, headers) => { console.log('Received event:', eventType); // Send to analytics, external logging, etc. }); ``` ### Run the Example ```bash node examples/with-callbacks.js ``` ## Gogs Webhook Configuration 1. Go to your Gogs repository settings 2. Navigate to Webhooks → Add Webhook → Gogs 3. Configure: - **Payload URL**: `http://your-server:3000/webhook` - **Content Type**: `application/json` - **Secret**: (optional) your webhook secret - **Events**: Select which events to receive 4. Click "Add Webhook" ### Supported Events - `push` - Repository push - `create` - Branch or tag creation - `delete` - Branch or tag deletion - `pull_request` - Pull request actions - `issues` - Issue actions - `issue_comment` - Issue comment actions - `release` - Release actions ## Project Structure ``` . ├── src/ │ ├── index.js # Main entry point │ ├── server.js # HTTP server implementation │ ├── webhookHandler.js # Webhook processing and callbacks │ ├── jobQueue.js # Persistent job queue with FIFO processing │ ├── commandExecutor.js # Command execution with variable substitution │ ├── config.js # Configuration loader (.env and commands.json) │ └── logger.js # Logging utility ├── client/ # TypeScript-based Claude Agent SDK client │ ├── src/ │ │ ├── index.ts # CLI entry point │ │ ├── claude-client.ts # Main Claude Agent SDK client │ │ ├── config.ts # Configuration management │ │ ├── mcpConfigManager.ts # MCP configuration manager │ │ └── types.ts # TypeScript type definitions │ └── dist/ # Compiled JavaScript (auto-generated) ├── scripts/ │ └── handle-issue-ts.sh # TypeScript issue handler script ├── examples/ │ ├── with-callbacks.js # Example with custom callbacks │ ├── mcp-config-default.json # Example default MCP config │ ├── mcp-config-multi-server.json # Example multi-server MCP config │ └── MCP-CONFIG.md # MCP configuration guide ├── .env.example # Example environment configuration ├── commands.json.example # Example command configuration ├── .env # Your environment configuration (not in git) ├── commands.json # Your command configuration (not in git) ├── queue.json # Queue state (auto-generated, gitignored) ├── package.json └── README.md ``` ## Architecture ### WebhookServer (`src/server.js`) - Creates HTTP server - Handles routing and request parsing - Manages webhook verification - Delegates webhook processing to handler ### WebhookHandler (`src/webhookHandler.js`) - Manages callback registration - Executes callbacks for events - Supports event-specific and global callbacks - Provides error handling for callbacks ### JobQueue (`src/jobQueue.js`) - Manages persistent job queue with FIFO processing - Ensures only one job executes at a time - Persists queue state to `queue.json` - Handles crash recovery and job resumption - Provides queue statistics and status ### CommandExecutor (`src/commandExecutor.js`) - Executes shell and Node.js commands on webhook events - Performs variable substitution in commands and arguments - Supports both shell scripts and Node.js scripts - Handles command timeouts and errors - Extracts webhook payload data ### Logger (`src/logger.js`) - Formats and colorizes console output - Provides structured logging methods - Handles timestamp formatting ### Config (`src/config.js`) - Loads and parses .env file and commands.json - Provides configuration access methods - Merges environment variables with file config - Supports backward compatibility with legacy .env commands ## TypeScript Client for Issue Automation The `client/` directory contains a TypeScript-based Claude Agent SDK client for AI-powered issue automation. ### Features - **AI-Powered Issue Processing**: Automatically processes Gogs issues assigned to Claude - **MCP Server Integration**: Connects to MCP servers for external API access - **Per-Repository Configuration**: Different repositories can use different MCP servers - **Environment Variable Substitution**: Support for secrets using `${VAR_NAME}` syntax - **Secure Configuration Storage**: MCP configs stored outside repositories with restricted permissions ### Quick Start ```bash cd client npm install npm run build ``` ### MCP Configuration The client supports three levels of MCP server configuration: 1. **Repository-specific**: `~/.config/agent-manager/mcp/{owner}/{repo}.json` 2. **Global default**: `~/.config/agent-manager/mcp/default.json` 3. **Hardcoded fallback**: Built-in gogs-mcp (automatic) **Example MCP Configuration:** ```json { "mcpServers": { "gogs": { "type": "stdio", "command": "node", "args": ["/data/gogs-mcp/dist/index.js"], "env": {} }, "database": { "type": "stdio", "command": "node", "args": ["/path/to/db-mcp/dist/index.js"], "env": { "DB_HOST": "${DB_HOST}", "DB_PASSWORD": "${DB_PASSWORD}" } } } } ``` **Key Features:** - Multiple MCP servers per repository - Environment variable substitution for secrets - Secure storage with 600 file permissions - Backward compatible with existing setup For detailed MCP configuration guide, see `examples/MCP-CONFIG.md`. ### Usage The TypeScript client is automatically invoked by the webhook server when issues are assigned to Claude. It can also be run manually: ```bash node client/dist/index.js [comment_body] [assignee] ``` See `client/README.md` for more details. ## Testing You can test the webhook server using curl: ```bash # Test health check curl http://localhost:3000/health # Test webhook with push event curl -X POST http://localhost:3000/webhook \ -H "Content-Type: application/json" \ -H "X-Gogs-Event: push" \ -H "X-Gogs-Delivery: 12345" \ -d '{ "ref": "refs/heads/main", "repository": { "name": "test-repo", "full_name": "user/test-repo" }, "pusher": { "username": "testuser" }, "commits": [ { "id": "abc123", "message": "Test commit" } ] }' ``` ## License ISC