Metadata-Version: 2.4
Name: git-catcher
Version: 0.4.0
Summary: Smee.io-based GitHub push webhook auto-deploy CLI tool
License: MIT
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: httpx>=0.27.0
Requires-Dist: python-dotenv>=1.0.0
Requires-Dist: InquirerPy>=0.3.4
Requires-Dist: rich>=13.0.0
Provides-Extra: dev
Requires-Dist: hypothesis>=6.100.0; extra == "dev"
Requires-Dist: pytest>=8.0.0; extra == "dev"

# Git Catcher

A Python CLI tool that receives GitHub push webhooks in real time via the Smee.io SSE proxy and automatically runs `git pull` and a custom deploy command for allowed branches.

Works behind firewalls/NAT, and supports automatic reconnection with exponential backoff on disconnection.

## Installation

```bash
pip install -r git_catcher/requirements.txt
```

### Dependencies

| Package | Purpose |
|---------|---------|
| `httpx` | SSE stream HTTP client |
| `rich` | Colored console logging |
| `python-dotenv` | Load environment variables from `.env` |
| `InquirerPy` | Interactive CLI prompts (init command) |
| `hypothesis` | Property-based testing (dev) |
| `pytest` | Test framework (dev) |

## Quick Start

### 1. Interactive Setup (recommended)

```bash
python -m git_catcher.main init
```

Or using the shortcut:

```bash
python run.py init
```

Answer the interactive prompts to automatically generate a `.env` file and `docker-compose.yml`.

- Auto-generate Smee.io URL (opens browser)
- Randomly generate Webhook Secret
- Select repository path and allowed branches
- Configure Slack notifications

### 2. Manual Setup

Copy `.env.example` to create a `.env` file and fill in the values.

```bash
cp git_catcher/.env.example git_catcher/.env
```

| Variable | Default | Description |
|----------|---------|-------------|
| `SMEE_URL` | `https://smee.io/YOUR_CHANNEL_ID` | Smee.io channel URL (create at [smee.io/new](https://smee.io/new)) |
| `WEBHOOK_SECRET` | `""` | GitHub Webhook Secret (signature verification skipped if not set) |
| `REPO_PATH` | `/path/to/your/repo` | Local path to the target Git repository |
| `ALLOWED_BRANCHES` | `main,master` | Branches to deploy (comma-separated) |
| `POST_PULL_COMMAND` | `""` | Shell command to run after `git pull` |
| `RECONNECT_DELAY` | `1` | Initial reconnect wait time (seconds) |
| `MAX_RECONNECT_DELAY` | `300` | Maximum reconnect wait time (seconds) |
| `ALLOWED_REPOS` | `""` | Allowed repositories (`owner/repo` format, comma-separated). Auto-detected from `REPO_PATH` git remote origin if empty |
| `SLACK_WEBHOOK_URL` | `""` | Slack Incoming Webhook URL. Notifications skipped if empty |
| `NOTIFY_ON_START` | `true` | Send Slack notification on startup (`true`/`false`) |
| `POLL_INTERVAL` | `0` | Git status polling interval (seconds; 0 disables) |
| `LOG_LEVEL` | `INFO` | Log level (`DEBUG`, `INFO`, `WARNING`, `ERROR`) |
| `LOG_FILE` | `""` | Log file path (file logging disabled if empty) |
| `SHOW_ALL_EVENTS` | `false` | Log all push events (`true`/`false`) |

### 2. Create a Smee.io Channel

1. Go to [https://smee.io/new](https://smee.io/new).
2. A unique channel URL is automatically generated (e.g. `https://smee.io/AbCdEfGhIjKlMn`).
3. Copy this URL. Use the same value for the GitHub Webhook Payload URL and `SMEE_URL` in your `.env`.

> **Note:** The `git-catcher init` command automatically opens Smee.io in your browser.

### 3. Add a GitHub Repository Webhook

1. In your GitHub repository, go to **Settings** → **Webhooks** → **Add webhook**.
2. Configure as follows:

| Field | Value |
|-------|-------|
| Payload URL | Smee.io channel URL (e.g. `https://smee.io/AbCdEfGhIjKlMn`) |
| Content type | `application/json` |
| Secret | Same value as `WEBHOOK_SECRET` in `.env` |
| Which events? | Select "Just the push event" |
| Active | Checked |

3. Click **Add webhook** to save.

> **Note:** Leaving the Secret blank also skips signature verification in Git Catcher. Setting a Secret is recommended for security.

### 3-1. Receive Webhooks for All Repositories via GitHub App (optional)

> If you only need a single repository, use [3. Add a GitHub Repository Webhook](#3-add-a-github-repository-webhook) above.
> To receive push webhooks from **all repositories** in your personal account (including future ones), create a GitHub App.

#### 1) Create a GitHub App

Create an app at [GitHub Settings → Developer settings → GitHub Apps → New GitHub App](https://github.com/settings/apps/new).

| Field | Value | Notes |
|-------|-------|-------|
| App name | Any name | e.g. `my-git-catcher` |
| Homepage URL | `https://github.com/your-account` | Required for app identification |
| Webhook URL | Smee.io channel URL | Destination to receive webhook data |
| Webhook Secret | Same value as `WEBHOOK_SECRET` in `.env` | Shared key to prevent payload tampering between server and GitHub |

#### 2) Permissions and Event Subscriptions

At the bottom of the creation form, configure:

- **Repository Permissions** → **Contents**: Set to `Read-only` or higher (required to detect code changes)
- **Subscribe to events** → Check **Push** (webhook fires only on code pushes)

#### 3) Install the App on Your Account

This is the most important step. Creating the app alone is not enough — you must **install** it on your account.

1. On the created app page, click **Install App**.
2. Select your account and choose **All repositories**.
3. Click **Install**.

> Selecting **All repositories** automatically applies the webhook to all existing repositories and any created in the future.

#### 4) Security and Verification

| Item | Description |
|------|-------------|
| Private Key (`.pem`) | Generate from the app settings page. Used for authentication when calling the GitHub API from your server |
| Recent Deliveries | View sent webhook payloads and test redelivery via **Redeliver** in the app settings → **Advanced** tab |

### 4. .env Example

`SMEE_URL` must match the channel URL from Smee.io, and `WEBHOOK_SECRET` must match the Secret entered in GitHub Webhook settings.

```dotenv
SMEE_URL=https://smee.io/AbCdEfGhIjKlMn
WEBHOOK_SECRET=your-webhook-secret-here
REPO_PATH=/path/to/your/repo
ALLOWED_BRANCHES=main,master
POST_PULL_COMMAND=
ALLOWED_REPOS=
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T.../B.../xxx
NOTIFY_ON_START=true
POLL_INTERVAL=0
LOG_LEVEL=INFO
LOG_FILE=
SHOW_ALL_EVENTS=false
```

See the [environment variables table](#2-manual-setup) for details on each variable.

## Running

### Basic Usage

```bash
# Option 1: run as module
python -m git_catcher.main

# Option 2: shortcut (recommended)
python run.py
```

On startup, git-catcher connects to the Smee.io SSE stream and waits for GitHub push events. When a push is detected on an allowed branch, it automatically runs `git fetch` → `git checkout` → `git pull` → post-pull command in sequence.

### Startup Notification

When `NOTIFY_ON_START=true`, git-catcher sends a Slack notification on startup containing:

- Repository name
- Allowed branch list
- Hostname
- External IP address
- Start time (UTC)

### CLI Commands

| Command | Description |
|---------|-------------|
| `git-catcher init` | Interactive setup (generates `.env` and `docker-compose.yml`) |
| `git-catcher run` | Run git-catcher (default behavior) |

### CLI Options (run command)

| Option | Description |
|--------|-------------|
| `-v` | DEBUG logging for `git_catcher` module only |
| `-vv` | DEBUG logging for all modules |
| `-vvvv` | Also print raw webhook payloads |
| `--show-all-events` | Log all push events (deployment filtering still applies) |
| `--log-file PATH` | Log file path (also writes DEBUG logs to file when set) |
| `--log-level LEVEL` | Log level (`DEBUG`, `INFO`, `WARNING`, `ERROR`) |

```bash
# Basic run
python run.py

# git_catcher module DEBUG + write to log file
python run.py -v --log-file deploy.log

# Show all push events + full DEBUG
python run.py -vv --show-all-events

# Interactive setup
python run.py init
```

### Key Features

- **Interactive setup**: Answer prompts with `git-catcher init` to auto-generate `.env` and `docker-compose.yml`
- **Quick start**: Run with `python run.py`
- **Startup notification**: Sends a Slack notification on start when `NOTIFY_ON_START=true` (includes repo, branches, host, IP)
- **Auto repo detection**: Extracts `owner/repo` from `REPO_PATH` git remote origin URL when `ALLOWED_REPOS` is not set
- **Slack deploy notifications**: Notifies a Slack channel on deploy success/failure when `SLACK_WEBHOOK_URL` is set
- **SSE reconnect recovery**: Resumes from the last received event using `Last-Event-ID` on disconnection
- **Rich console logging**: Readable colored output with timestamp (HH:MM:SS.ms) format

## Verifying It Works

1. [Run](#running) Git Catcher:

   ```bash
   python run.py
   ```

2. When you see the `SSE stream connected` log, the connection is established.
3. If `NOTIFY_ON_START=true`, a startup notification is sent to Slack.
4. Push a commit to an allowed branch:

   ```bash
   git add .
   git commit -m "test webhook"
   git push origin main
   ```

5. Verify the following logs appear in order in the Git Catcher terminal:
   - Push detected: `[main] test webhook`
   - `git fetch` → `git checkout` → `git pull` complete
   - `POST_PULL_COMMAND` executed (if configured)
   - Slack notification sent (if configured)

If something is wrong, check the **Recent Deliveries** tab on the GitHub Webhook page to inspect delivery status.

## Tests

```bash
pytest tests/ -v
```
