Skip to content

tionis/patchwork

Repository files navigation

Patchwork

CI Test codecov Go Report Card License: MIT

A simple communication backend for scripts and other small applications. Patchwork enables IFTTT-type applications by providing infinite HTTP endpoints that serve as a multi-process, multi-consumer (MPMC) queue.

Features

  • Channel-based Communication: HTTP endpoints that act as communication channels
  • Multiple Operating Modes: Queue, pubsub, and request-responder patterns for different use cases
  • Multiple Namespaces: Public (/p), hooks (/h, /r), user (/u/{username}) namespaces
  • Notification System: Built-in notification backend support (Matrix, Discord, etc.)
  • WebSocket Tunneling: SSH/TCP tunneling via HuProxy integration
  • Token-based Authentication: Forgejo-integrated ACL system with caching
  • Administrative API: Cache invalidation and user management endpoints
  • Prometheus Metrics: Secured metrics endpoint for monitoring with authentication
  • Rate Limiting: Built-in rate limiting for public namespaces to prevent abuse

What does it do?

Patchwork provides infinite HTTP endpoints that can be used to implement powerful serverless applications - including desktop notifications, SMS notifications, job queues, web hosting, and file sharing. These applications are basically just a few lines of bash that wrap a curl command.

The philosophy behind this is that the main logic happens on the local machine with small scripts. There is a server with an infinite number of virtual channels that will relay messages between the publisher and the subscriber.

Quick Start

Basic Usage

To subscribe to a channel you can simply make a GET request:

curl https://patchwork.example.com/p/a61b1f42

The above will block until something is published to the channel a61b1f42. You can easily publish to a channel using a POST request:

curl https://patchwork.example.com/p/a61b1f42 -d "hello, world"

The subscriber will immediately receive that data. If you reverse the order, then the post will block until it is received.

Pubsub mode

The default mode is a MPMC queue, where the first to connect are able to publish/subscribe. But you can also specify publish-subscribe (pubsub) mode. In pubsub mode, the publisher will become non-blocking and their data will be transmitted to each connected subscriber:

curl https://patchwork.example.com/p/a61b1f42?pubsub=true -d "hello, world"

Publish with GET

You can also publish with a GET request by using the parameter body=X, making it easier to write href links that can trigger hooks:

curl https://patchwork.example.com/p/a61b1f42?pubsub=true&body=hello,%20world

Request-Responder mode

Patchwork supports a request-responder pattern using /req/... and /res/... subnamespaces. This enables HTTP request/response communication through the relay:

# Responder waits for requests
curl https://patchwork.example.com/public/res/api/users

# Requester sends request (blocks until response)
curl -X POST -d '{"name":"Alice"}' https://patchwork.example.com/public/req/api/users

The request-responder mode supports all HTTP methods, header passthrough (including Patch-Uri for request path information), and a "double clutch" mode using ?switch=true that allows dynamic response routing.

Namespaces

The server is organized by namespaces with different access patterns. Each namespace now supports structured sub-paths that determine the communication behavior:

Namespace Structure

All namespaces (public, user, etc.) now support the following sub-paths:

  • /_/... → Special control endpoints (user-owned namespaces only)
  • /./... → Flexible space - defaults to blocking/queue behavior, can be switched to pubsub with ?pubsub=true
  • /pubsub/... → All requests use pubsub behavior (non-blocking, broadcast to all consumers)
  • /queue/... → All requests use blocking/queue behavior (one-to-one communication)
  • /req/... → Request side of request-responder pattern
  • /res/... → Response side of request-responder pattern (pairs with matching /req/ paths)

Available Namespaces

  • /public/**: Public namespace - no authentication required. Everyone can read and write. Perfect for testing and public communication channels.
    • Example: /public/queue/notifications (blocking), /public/pubsub/events (broadcast)
  • /p/**: Legacy public namespace - maintained for backward compatibility. Maps to the public namespace with default behavior.
  • /h/**: Forward hooks - GET /h to obtain a new channel and secret, then use the secret to POST data to that channel. Anyone can GET data from the channel. Useful for webhooks and notifications where you want to control who can send.
  • /r/**: Reverse hooks - GET /r to obtain a new channel and secret, then anyone can POST data to that channel. Use the secret to GET data from the channel. Useful for collecting data from multiple sources where you want to control who can read.
  • /u/{username}/**: User namespace - controlled by ACL lists. Access is controlled by YAML ACL files stored in Forgejo/Gitea repositories that specify which tokens can access which paths. Includes notification endpoints (/_/ntfy) for sending alerts and messages.
  • /huproxy/{user}/{host}/{port}: HTTP-to-TCP WebSocket proxy for tunneling SSH and other protocols. Based on Google's HUProxy project, this endpoint provides WebSocket tunneling primarily for SSH connections. Uses token-based authentication via Authorization header.

Behavior Patterns

Patchwork supports behavior determination based on the path structure, providing more predictable and explicit communication patterns:

Queue Behavior (Blocking)

Paths: /queue/... or /./... (default)

In queue mode, producers will block until a consumer is available to receive the data. This ensures one-to-one communication and guarantees message delivery.

# Producer blocks until consumer connects
curl https://patchwork.example.com/public/queue/jobs -d "process-file.txt"

# Consumer receives the message
curl https://patchwork.example.com/public/queue/jobs

Pubsub Behavior (Non-blocking)

Paths: /pubsub/... or /./...?pubsub=true

In pubsub mode, producers send data immediately and don't wait for consumers. All connected consumers receive the same message (broadcast).

# Producer sends immediately (non-blocking)
curl https://patchwork.example.com/public/pubsub/events -d "user-login"

# Multiple consumers can receive the same event
curl https://patchwork.example.com/public/pubsub/events  # Consumer 1
curl https://patchwork.example.com/public/pubsub/events  # Consumer 2

Flexible Behavior

Paths: /./...

The flexible space defaults to queue behavior but can be switched to pubsub with the ?pubsub=true query parameter:

# Default: queue behavior (blocking)
curl https://patchwork.example.com/public/./notifications -d "alert"

# Override: pubsub behavior (non-blocking)
curl https://patchwork.example.com/public/./notifications?pubsub=true -d "broadcast"

Passthrough Headers

Patchwork supports passthrough headers using the Patch-H-* prefix system, allowing you to forward original request context between clients through the relay system.

How It Works

  • Request Headers: Headers starting with Patch-H- represent original headers from the requester
  • Response Headers: Patch-H-* headers are stripped of their prefix and passed through to the final receiver
  • Automatic Headers: Common headers like User-Agent, Accept, etc. are automatically converted to Patch-H-* format

Example Usage

Producer side (sending headers):

curl -X POST \
  -H "Patch-H-Original-IP: 192.168.1.100" \
  -H "Patch-H-User-ID: alice123" \
  -H "User-Agent: MyApp/1.0" \
  -d "request data" \
  https://patchwork.example.com/public/queue/api

Consumer side (receiving headers):

curl -v https://patchwork.example.com/public/queue/api
# Response includes:
# Original-IP: 192.168.1.100
# User-ID: alice123
# User-Agent: MyApp/1.0

This enables building proxy-like applications where the original request context is preserved through the relay.

Authentication

Patchwork uses a Forgejo-integrated authentication system. Each user maintains a config.yaml file in their .patchwork repository to define access tokens, permissions, notification settings, and HuProxy access.

User Configuration (config.yaml)

For user namespaces (/u/{username}/), create a .patchwork repository with a config.yaml file:

# Unified config.yaml structure - tokens directly at root level
tokens:
  "my_token_name":
    is_admin: false
    POST: 
      - "/projects/*/data"  # Can POST to any project's data endpoint
      - "/_/ntfy"          # Can send notifications
    GET: 
      - "/projects/myproject/*"  # Can GET from all paths under myproject
      - "!/projects/myproject/secret/*" # But nothing in my secret project
    huproxy:
      - "*.example.com:*"  # Can access specific hosts via HuProxy
      - "localhost:*"

  "restricted_token":
    is_admin: false
    POST: []  # No POST access
    GET: 
      - "*"  # Can GET from all subpaths in this namespace

  "webhook_token":
    is_admin: false
    POST: 
      - "/webhooks/*"  # Can POST to any webhook endpoint
      - "/_/ntfy"      # Can send notifications
    GET: 
      - "/status"  # Can only GET the status endpoint

  "admin_token":
    is_admin: true
    POST: ["*"]
    GET: ["*"]

# Optional: Configure notification backend
ntfy:
  type: matrix
  config:
    access_token: "your_matrix_access_token"
    user: "@bot:matrix.org"
    endpoint: "https://matrix.org"  # optional: Matrix server endpoint
    room_id: "!roomid:matrix.org"   # optional: specific room ID

Permission System

Each token can have POST, GET, and huproxy permissions defined with glob patterns:

  • Empty array ([]) denies all access for that method
  • "*" allows access to all subpaths
  • Specific glob patterns like projects/*/data allow fine-grained control
  • Negation patterns like !secret/* can exclude specific paths
  • Admin tokens (is_admin: true) have access to administrative endpoints

Public Token Behavior

When no token is provided in user namespaces, the request is treated as using a "public" token. This allows users to create public endpoints within their namespace:

tokens:
  "public":
    is_admin: false
    GET: 
      - "/status"     # Allow public status checks
      - "/health"     # Allow public health checks
    POST: []          # No public POST access
  
  "private_token":
    is_admin: false
    GET: ["*"]        # Full read access with token
    POST: ["/data/*"] # Write access to data endpoints

Examples:

# Public access (no token needed, uses "public" token)
curl https://patchwork.example.com/u/alice/status

# Authenticated access
curl https://patchwork.example.com/u/alice/data/logs?token=private_token -d "log entry"

If no "public" token is defined, requests without authentication will be denied with "token not found".

HuProxy Access

HuProxy access is configured within each token's definition using the huproxy field:

tokens:
  "production_ssh_token":
    huproxy:
      - "*.production.com:*"
  "development_access":
    huproxy:
      - "*.dev.com:*"
      - "localhost:*"
  "backup_script_token":
    huproxy:
      - "backup.example.com:22"

Notification System

The notification endpoint is available at /u/{username}/_/ntfy and supports:

  • Multiple message types: plain, markdown, html
  • Various input methods: JSON POST, form POST, GET with query parameters
  • Backend integration: Matrix, Discord, and other notification services

Example notification usage:

curl -X POST "https://patchwork.example.com/u/username/_/ntfy" \
  -H "Authorization: Bearer your-token" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "markdown",
    "title": "Alert",
    "message": "Something **important** happened!"
  }'

Repository Setup

  1. Create .patchwork repository: Each user/organization creates a repository named .patchwork
  2. Add config.yaml: Place the unified configuration in a file named config.yaml in the repository root
  3. Grant access: Give the special patchwork user read access to the .patchwork repository
  4. Caching: The patchwork server pulls and caches these configuration files as needed

Example repository structure:

user/.patchwork/
├── config.yaml
└── README.md (optional)

Examples

Namespace Behavior Examples

Queue behavior (one-to-one, blocking):

# Producer waits until consumer connects
curl https://patchwork.example.com/public/queue/jobs -d "encode-video.mp4"

# Consumer receives the job
curl https://patchwork.example.com/public/queue/jobs

Pubsub behavior (broadcast, non-blocking):

# Producer sends immediately to all consumers
curl https://patchwork.example.com/public/pubsub/events -d "user-login:alice"

# Multiple consumers can listen
curl https://patchwork.example.com/public/pubsub/events  # Logger service
curl https://patchwork.example.com/public/pubsub/events  # Analytics service

Flexible behavior with override:

# Default blocking behavior
curl https://patchwork.example.com/public/./alerts -d "server-down"

# Override to pubsub
curl https://patchwork.example.com/public/./alerts?pubsub=true -d "system-update"

Request-responder pattern:

# Responder waits for API requests
curl https://patchwork.example.com/public/res/api/users

# Requester sends GET request (blocks until response)
curl https://patchwork.example.com/public/req/api/users

# POST request with data and headers
curl -X POST \
  -H "Authorization: Bearer token123" \
  -d '{"name":"Alice"}' \
  https://patchwork.example.com/public/req/api/users

# Responder can set status code in response
curl -H "Patch-Status: 201" \
  -d '{"id":123,"name":"Alice"}' \
  https://patchwork.example.com/public/res/api/users

# Double clutch mode with dynamic response routing
# Terminal 1: Requester (blocks waiting for response)
curl -X POST -d '{"task":"process"}' https://patchwork.example.com/public/req/myservice

# Terminal 2: Responder in switch mode (body = new channel ID)
curl -X POST "https://patchwork.example.com/public/res/myservice?switch=true" -d "worker-123"
# Returns the request data and switches to channel "worker-123"

# Terminal 3: Send response on the new channel
curl -X POST -d '{"result":"completed"}' https://patchwork.example.com/public/worker-123
# Original requester receives this response

Using passthrough headers:

# Send with context headers
curl -X POST \
  -H "Patch-H-Client-IP: 10.0.1.5" \
  -H "Patch-H-Trace-ID: req-12345" \
  -d "api-request" \
  https://patchwork.example.com/public/queue/api

# Receive with original context
curl -v https://patchwork.example.com/public/queue/api
# Headers include: Client-IP: 10.0.1.5, Trace-ID: req-12345

File Sharing

Sending a file:

curl -X POST --data-binary "@test.txt" https://patchwork.example.com/public/queue/files

Receiving a file:

curl https://patchwork.example.com/public/queue/files > test.txt

Desktop Notifications (Linux)

#!/bin/bash
MAGIC="notify"
URL="https://patchwork.example.com/p/notifications"

while [ 1 ]
do
  X="$(curl $URL)"
  if [[ $X =~ ^$MAGIC ]]; then
    Y="$(echo "$X" | sed "s/$MAGIC*//")"
    notify-send "$Y"
  else
    sleep 10
  fi
done

Job Queue

Adding jobs to a queue:

#!/bin/bash
for filename in *.mp3
do
  curl https://patchwork.example.com/p/jobs -d $filename
done

Processing jobs from the queue:

#!/bin/bash
while true
do
  filename=$(curl -s https://patchwork.example.com/p/jobs)
  if [ "$filename" != "Too Many Requests" ]
  then
    echo "Processing: $filename"
    # Process the file here
    ffmpeg -i "$filename" "$filename.ogg"
  else
    sleep 1
  fi
done

Forward Hook Example

To use forward hooks, first obtain a channel and secret by making a GET request to /h:

# Get a new channel and secret
curl https://patchwork.example.com/h
# Returns: {"channel":"abc123-def456-...","secret":"sha256hash..."}

Then use the channel and secret for secure communication:

# Send notification (requires secret)
curl https://patchwork.example.com/h/abc123-def456-...?secret=sha256hash... -d "Server is down!"

# Anyone can listen for notifications
curl https://patchwork.example.com/h/abc123-def456-...

Reverse Hook Example

Similarly, for reverse hooks, obtain a channel and secret by making a GET request to /r:

# Get a new channel and secret
curl https://patchwork.example.com/r
# Returns: {"channel":"xyz789-abc123-...","secret":"sha256hash..."}

Then collect data from multiple sources:

# Anyone can submit metrics
curl https://patchwork.example.com/r/xyz789-abc123-... -d "cpu:85%"
curl https://patchwork.example.com/r/xyz789-abc123-... -d "memory:67%"

# Reading requires secret
curl https://patchwork.example.com/r/xyz789-abc123-...?secret=sha256hash...

User Namespace Example

Using ACL-controlled user namespaces with tokens:

# Send data to a user namespace (requires appropriate token)
curl https://patchwork.example.com/u/alice/projects/web/logs?token=webhook_token -d "Deploy completed"

# Read from user namespace (requires token with GET permission)
curl https://patchwork.example.com/u/alice/projects/web/status?token=some_token_name

SSH over WebSocket Tunneling

Using the huproxy endpoint to tunnel SSH through HTTP/HTTPS:

# Using token-based authentication
curl -H "Authorization: Bearer your_huproxy_token" \
  https://patchwork.example.com/huproxy/alice/localhost/22

# With SSH client (requires huproxyclient tool)
ssh -o 'ProxyCommand=huproxyclient -auth=Bearer:your_token wss://patchwork.example.com/huproxy/alice/targethost/22' user@targethost

Installation

Docker

docker run -d \
  -p 8080:8080 \
  -e SECRET_KEY="your-secret-key" \
  -e FORGEJO_TOKEN="your-forgejo-token" \
  -e FORGEJO_URL="https://git.example.com" \
  ghcr.io/tionis/patchwork:latest

From Source

git clone https://github.com/tionis/patchwork.git
cd patchwork
go build -o patchwork .
./patchwork start --port 8080

Environment Variables

  • SECRET_KEY: Secret key for HMAC generation (required)
  • FORGEJO_TOKEN: API token for accessing Forgejo/Gitea (required)
  • FORGEJO_URL: URL of your Forgejo/Gitea instance (default: https://forge.tionis.dev)
  • ACL_TTL: Cache duration for ACL files (default: 5m)
  • LOG_LEVEL: Logging level (DEBUG, INFO, WARN, ERROR)
  • LOG_SOURCE: Add source information to logs (true/false)

Forgejo/Gitea Backend Setup

To enable user namespaces with ACL control:

  1. Set up Forgejo/Gitea instance: Ensure you have a running Forgejo or Gitea server
  2. Create patchwork user: Create a dedicated patchwork user account on your Forgejo/Gitea instance
  3. Generate API token: Create an API token for the patchwork user
  4. Configure environment: Set FORGEJO_URL and FORGEJO_TOKEN environment variables
  5. User setup: Users create .patchwork repositories and grant read access to the patchwork user

Health Checks and Monitoring

Patchwork includes health check endpoints and monitoring capabilities:

  • /healthz: Returns "OK!" if the server is running
  • /status: Alias for /healthz
  • /metrics: Prometheus metrics endpoint with authentication

Metrics Endpoint

The /metrics endpoint provides Prometheus-compatible metrics for monitoring server performance, request patterns, and system health. The endpoint includes comprehensive security controls:

Authentication

The metrics endpoint uses multi-layered authentication:

  • Local Access: Requests from localhost (127.0.0.1, ::1) are allowed without authentication for local monitoring tools
  • Remote Access: Requires authentication using the server's Forgejo token
  • Token Formats: Supports Bearer <token>, token <token>, or direct token in Authorization header
  • Query Parameter: Token can also be passed as ?token=<token> query parameter

Usage Examples

# Local access (no authentication needed)
curl http://localhost:8080/metrics

# Remote access with Bearer token
curl -H "Authorization: Bearer your-forgejo-token" https://patchwork.example.com/metrics

# Remote access with query parameter
curl https://patchwork.example.com/metrics?token=your-forgejo-token

Available Metrics

  • patchwork_http_requests_total: Total number of HTTP requests by method, namespace, and status code
  • patchwork_http_request_duration_seconds: HTTP request duration histograms
  • patchwork_channels_total: Current number of active channels
  • patchwork_active_connections: Number of active WebSocket/long-polling connections
  • patchwork_messages_total: Total messages processed by namespace and behavior
  • patchwork_message_size_bytes: Message size histograms
  • patchwork_auth_requests_total: Authentication attempts by result
  • patchwork_cache_operations_total: Cache hit/miss statistics

Monitoring Setup

For production monitoring, configure your monitoring system (Prometheus, Grafana, etc.) to scrape the metrics endpoint:

# prometheus.yml
scrape_configs:
  - job_name: 'patchwork'
    static_configs:
      - targets: ['patchwork.example.com:80']
    scheme: https
    authorization:
      credentials: "your-forgejo-token"
    metrics_path: /metrics

For Docker deployments, a health check is automatically configured.

License

MIT curl -H "Authorization: Bearer your_secure_token_here" --http1.1 --upgrade websocket https://patchwork.example.com/huproxy/alice/localhost/22


#### Token-based Authentication

Authentication is managed through `config.yaml` files stored in each user's `.patchwork` repository:

```yaml
# .patchwork/config.yaml
tokens:
  "production_ssh_token_abc123":
    huproxy:
      - "*.production.com:*"
  "development_access_def456":
    huproxy:
      - "*.dev.com:*"
      - "localhost:*"
  "backup_script_token_789xyz":
    huproxy:
      - "backup.example.com:22"

Security Notes:

  • Tokens are validated against the user's .patchwork/config.yaml file
  • Each user controls their own token list through their repository
  • Tokens should be long, random strings (recommended: 32+ characters)
  • The proxy supports any TCP service, not just SSH (databases, VNC, etc.)

Original Project: This implementation is based on Google's HUProxy with added user-specific authentication and integration into the Patchwork ecosystem.TP endpoints that can be used to implement powerful serverless applications - including desktop notifications, SMS notifications, job queues, web hosting, and file sharing. These applications are basically just a few lines of bash that wrap a curl command.

The philosophy behind this is that the main logic happens on the local machine with small scripts. There is a server with an infinite number of virtual channels that will relay messages between the publisher and the subscriber.

Quick Start

Basic Usage

To subscribe to a channel you can simply make a GET request:

curl https://patchwork.example.com/p/a61b1f42

The above will block until something is published to the channel a61b1f42. You can easily publish to a channel using a POST request:

curl https://patchwork.example.com/p/a61b1f42 -d "hello, world"

The subscriber will immediately receive that data. If you reverse the order, then the post will block until it is received.

Pubsub mode

The default mode is a MPMC queue, where the first to connect are able to publish/subscribe. But you can also specify publish-subscribe (pubsub) mode. In pubsub mode, the publisher will become non-blocking and their data will be transmitted to each connected subscriber:

curl https://patchwork.example.com/p/a61b1f42?pubsub=true -d "hello, world"

Publish with GET

You can also publish with a GET request by using the parameter body=X, making it easier to write href links that can trigger hooks:

curl https://patchwork.example.com/p/a61b1f42?pubsub=true&body=hello,%20world

Namespaces

The server is organized by namespaces with different access patterns:

  • /p/**: Public namespace - no authentication required. Everyone can read and write. Perfect for testing and public communication channels.
  • /h/**: Forward hooks - GET /h to obtain a new channel and secret, then use the secret to POST data to that channel. Anyone can GET data from the channel. Useful for webhooks and notifications where you want to control who can send.
  • /r/**: Reverse hooks - GET /r to obtain a new channel and secret, then anyone can POST data to that channel. Use the secret to GET data from the channel. Useful for collecting data from multiple sources where you want to control who can read.
  • /u/{username}/**: User namespace - controlled by ACL lists (not implemented yet). Access is controlled by YAML ACL files stored in Forgejo/Gitea repositories that specify which tokens can access which paths.
  • /huproxy/{user}/{host}/{port}: HTTP-to-TCP WebSocket proxy for tunneling SSH and other protocols. Based on Google's HUProxy project, this endpoint provides WebSocket tunneling primarily for SSH connections. Uses token-based authentication via Authorization header. Tokens are managed through the huproxy field in the user's config.yaml file in their .patchwork repository.

If a request is made to a user namespace without an Authorization header or toke query parameter, it is treated as a request with the token public. This allows for creating public endpoints within a user's namespace that can be accessed without authentication.

ACL File Format

For user namespaces, access control is managed through YAML files stored in Forgejo/Gitea repositories. Each user or organization can create a .patchwork repository containing an config.yaml file:

  • config.yaml: Defines ACL permissions and HuProxy tokens for different authentication tokens

User Namespace ACL (config.yaml)

some_token_name:
  POST: "projects/*/data"  # Can POST to any project's data endpoint
  GET: "projects/myproject/**"  # Can GET from all paths under myproject

restricted_token:
  POST: ""  # Empty string means no POST access allowed
  GET: "**"  # Can GET from all subpaths in this namespace

webhook_token:
  POST: "webhooks/*"  # Can POST to any webhook endpoint
  GET: "status"  # Can only GET the status endpoint

Each token can have POST and GET permissions defined with glob patterns:

  • An empty string ("") denies all access for that method
  • ** allows access to all subpaths
  • Specific glob patterns like projects/*/data allow fine-grained control
  • Tokens are passed via the token query parameter: ?token=some_token_name

Repository Setup

  1. Create .patchwork repository: Each user/organization creates a repository named .patchwork
  2. Add config.yaml: Place the ACL and HuProxy configuration in a file named config.yaml in the repository root
  3. Grant access: Give the special patchwork user read access to the .patchwork repository
  4. Caching: The patchwork server pulls and caches these configuration files as needed

Example repository structure:

user/.patchwork/
├── config.yaml
└── README.md (optional)

HuProxy Configuration

For HuProxy access, configure tokens in the huproxy field of your .patchwork/config.yaml file:

tokens:
  "some-long-token-for-huproxy-access":
    huproxy:
      - "*"  # allows all host:port combinations
  "restricted-huproxy-token":
    huproxy:
      - "*.example.com:*"
      - "localhost:*"

Users can then access HuProxy endpoints using these tokens:

curl -H "Authorization: Bearer some-long-token-for-huproxy-access" \
  https://patchwork.example.com/huproxy/alice/localhost/22

Modes

Each endpoint supports multiple modes:

  • queue: Each message is received by exactly one receiver (default)
  • pubsub: All receivers receive the published message
  • req/res: Request/response pattern (not implemented yet)

Examples

File Sharing

Sending a file:

curl -X POST --data-binary "@test.txt" https://patchwork.example.com/p/test.txt

Receiving a file:

wget https://patchwork.example.com/p/test.txt

Desktop Notifications (Linux)

#!/bin/bash
MAGIC="notify"
URL="https://patchwork.example.com/p/notifications"

while [ 1 ]
do
  X="$(curl $URL)"
  if [[ $X =~ ^$MAGIC ]]; then
    Y="$(echo "$X" | sed "s/$MAGIC*//")"
    notify-send "$Y"
  else
    sleep 10
  fi
done

Job Queue

Adding jobs to a queue:

#!/bin/bash
for filename in *.mp3
do
  curl https://patchwork.example.com/p/jobs -d $filename
done

Processing jobs from the queue:

#!/bin/bash
while true
do
  filename=$(curl -s https://patchwork.example.com/p/jobs)
  if [ "$filename" != "Too Many Requests" ]
  then
    echo "Processing: $filename"
    # Process the file here
    ffmpeg -i "$filename" "$filename.ogg"
  else
    sleep 1
  fi
done

Forward Hook Example

To use forward hooks, first obtain a channel and secret by making a GET request to /h:

# Get a new channel and secret
curl https://patchwork.example.com/h
# Returns: {"channel":"abc123-def456-...","secret":"sha256hash..."}

Then use the channel and secret for secure communication:

# Send notification (requires secret)
curl https://patchwork.example.com/h/abc123-def456-...?secret=sha256hash... -d "Server is down!"

# Anyone can listen for notifications
curl https://patchwork.example.com/h/abc123-def456-...

Reverse Hook Example

Similarly, for reverse hooks, obtain a channel and secret by making a GET request to /r:

# Get a new channel and secret
curl https://patchwork.example.com/r
# Returns: {"channel":"xyz789-abc123-...","secret":"sha256hash..."}

Then collect data from multiple sources:

# Anyone can submit metrics
curl https://patchwork.example.com/r/xyz789-abc123-... -d "cpu:85%"
curl https://patchwork.example.com/r/xyz789-abc123-... -d "memory:67%"

# Reading requires secret
curl https://patchwork.example.com/r/xyz789-abc123-...?secret=sha256hash...

Note: The secrets are generated using HMAC-SHA256 with a server secret key and the channel name. If no SECRET_KEY environment variable is provided, a random key is generated at startup (secrets won't persist across server restarts).

User Namespace Example

Using ACL-controlled user namespaces with tokens:

# Send data to a user namespace (requires appropriate token)
curl https://patchwork.example.com/u/alice/projects/web/logs?token=webhook_token -d "Deploy completed"

# Read from user namespace (requires token with GET permission)
curl https://patchwork.example.com/u/alice/projects/web/status?token=some_token_name

HTTP-to-TCP Proxy (huproxy) Example

Using the huproxy endpoint to proxy HTTP requests to TCP services:

# Using token-based authentication
curl -H "Authorization: Bearer your_huproxy_token" \
  https://patchwork.example.com/huproxy/alice/localhost/22

# Alternative without "Bearer" prefix  
curl -H "Authorization: your_huproxy_token" \
  https://patchwork.example.com/huproxy/alice/database/5432

Note: Tokens are configured in the huproxy field of the config.yaml file in the user's .patchwork repository.

Tools

Bash Client

You can download a bash-based client here.

Usage Examples

# Send data to a channel
patchwork send mychannel "hello world"
echo "hello" | patchwork send mychannel

# Receive data from a channel
patchwork receive mychannel

# Send in pubsub mode
patchwork send -m pubsub mychannel "broadcast message"

# Use forward hooks
patchwork get-hook h  # Get channel and secret for forward hook
patchwork send -n h -s <secret> <channel> "notification"

# Use reverse hooks
patchwork get-hook r  # Get channel and secret for reverse hook
patchwork send -n r <channel> "cpu:85%"  # No secret needed
patchwork receive -n r -s <secret> <channel>

# Use user namespaces with tokens
patchwork send -n u -t webhook_token alice/projects/logs "deploy completed"
patchwork receive -n u -t some_token alice/projects/status

# Listen for notifications
patchwork listen notifications

# Share files
patchwork share document.pdf
patchwork download document.pdf

Installation

Docker

docker run -p 8080:8080 ghcr.io/tionis/patchwork:latest

From Source

git clone https://github.com/tionis/patchwork.git
cd patchwork
go build -o patchwork .
./patchwork start --port 8080

CLI Options

The start command supports the following options:

  • --port: Port to listen on (default: 8080)

Example:

./patchwork start --port 3000

Configuration

Forgejo/Gitea Backend Setup

To enable user namespaces with ACL control, configure Patchwork to use a Forgejo or Gitea instance:

  1. Set up Forgejo/Gitea instance: Ensure you have a running Forgejo or Gitea server

  2. Create patchwork user: Create a dedicated patchwork user account on your Forgejo/Gitea instance

  3. Configure Patchwork: Set environment variables or configuration file to point to your Forgejo/Gitea instance:

    export FORGEJO_URL="https://git.example.com"
    export ACL_TTL="5m"  # Cache ACL files for 5 minutes
    export SECRET_KEY="your-secret-key-for-hook-authentication"  # For hook HMAC generation

    Note: The SECRET_KEY is used to generate HMAC-SHA256 secrets for hook channels. If not provided, a random key is generated at startup, but hook secrets won't persist across server restarts.

User Setup for ACL

For users who want to use the /u/{username}/ namespace:

  1. Create .patchwork repository in your Forgejo/Gitea account
  2. Add the patchwork user as a collaborator with read access
  3. Create config.yaml file with your token permissions
  4. Commit and push the configuration

The patchwork server will automatically fetch and cache your ACL configuration when needed.

License

MIT

Development

Test Coverage

Patchwork maintains comprehensive test coverage including:

  • Unit Tests: Authentication, metrics, utilities, and core functionality
  • Integration Tests: Full server workflows with mock Forgejo backend
  • Security Tests: Metrics endpoint authentication and access controls
  • Performance Tests: Benchmarks for concurrent access and load testing

To run the complete test suite:

# Run all tests
go test ./...

# Run with coverage report
go test -cover ./...

# Run integration tests specifically
go test ./internal/integration/

# Run security tests
go test -run TestSecured ./

Code Quality

The codebase follows Go best practices with:

  • Comprehensive error handling and logging
  • Structured code organization with clear separation of concerns
  • Extensive inline documentation and comments
  • Type safety and interface-driven design
  • Mock implementations for external dependencies in tests

About

A simple communication backend for webhook proxying and script communication

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •