Patchwork logo

Patchwork

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 include built-in metrics for monitoring, rate limiting for abuse prevention, and comprehensive authentication with token-based access control.

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.

Basics

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.

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

curl http://patch.tionis.dev/public/queue/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 http://patch.tionis.dev/public/queue/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 http://patch.tionis.dev/public/pubsub/a61b1f42 -d "hello, world"

Or use the flexible namespace with the pubsub parameter:

curl http://patch.tionis.dev/public/./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 http://patch.tionis.dev/p/a61b1f42?pubsub=true&body=hello,%20world

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 http://patch.tionis.dev/public/queue/jobs -d "process-file.txt"

# Consumer receives the message
curl http://patch.tionis.dev/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 http://patch.tionis.dev/public/pubsub/events -d "user-login"

# Multiple consumers can receive the same event
curl http://patch.tionis.dev/public/pubsub/events  # Consumer 1
curl http://patch.tionis.dev/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 http://patch.tionis.dev/public/./notifications -d "alert"

# Override: pubsub behavior (non-blocking)
curl http://patch.tionis.dev/public/./notifications?pubsub=true -d "broadcast"

Request-Responder Behavior

Paths: /req/... and /res/... subnamespaces

The request-responder pattern enables HTTP request/response communication through the patchwork relay. Requesters send to /req/... paths, and responders handle them via /res/... paths with the same subpath.

Basic Communication

Requesters and responders use matching subpaths. For example, /req/api/users pairs with /res/api/users.

# Responder waits for requests
curl http://patch.tionis.dev/public/res/api/users

# Requester sends a GET request (blocks until response)
curl http://patch.tionis.dev/public/req/api/users

HTTP Methods

All HTTP methods are supported (GET, POST, PUT, DELETE, PATCH, etc.). The method is preserved from requester to responder.

# Responder handles various methods
curl http://patch.tionis.dev/public/res/api/users

# POST request with data
curl -X POST -d '{"name":"Alice"}' http://patch.tionis.dev/public/req/api/users

# PUT request
curl -X PUT -d '{"id":1,"name":"Bob"}' http://patch.tionis.dev/public/req/api/users

# DELETE request
curl -X DELETE http://patch.tionis.dev/public/req/api/users/1

Double Clutch Mode

Use ?switch=true on the responder side to enable "double clutch" mode. In this mode, the responder receives the request, then switches to a new channel to send the response. This allows for dynamic response routing.

# Terminal 1: Start requester (will wait for response)
curl -X POST -d '{"data":"request"}' http://patch.tionis.dev/public/req/myservice

# Terminal 2: Responder in switch mode
# Body contains the new channel ID to switch to
curl -X POST "http://patch.tionis.dev/public/res/myservice?switch=true" -d "new-channel-123"
# This returns the request data to the responder

# Terminal 3: Send the actual response on the new channel
curl -X POST -d '{"result":"processed"}' http://patch.tionis.dev/public/new-channel-123
# The original requester receives this response

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

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" \
  http://patch.tionis.dev/public/queue/api

Consumer side (receiving headers):

curl -v http://patch.tionis.dev/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.

Request-Responder Headers

In request-responder mode, additional headers provide request context and response control:

Request-responder example:

# Responder receives request with context
curl http://patch.tionis.dev/public/res/api/users
# Request headers include:
# Patch-Uri: /api/users/123
# Patch-H-Authorization: Bearer token123

# Responder can set status in response
curl -H "Patch-Status: 201" -d '{"created": true}' http://patch.tionis.dev/public/res/api/users

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:

Available Namespaces

User Namespaces

For user namespaces (/u/{username}/), access is controlled by tokens that you configure in your own .patchwork repository. Create a .patchwork repository in your account and add a config.yaml file to define tokens, permissions, notification settings, and HuProxy access.

If a request is made to a user namespace without an Authorization header or token 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.

config.yaml Format

Create a config.yaml file in your .patchwork repository:

tokens:
  "my_token_name":
    is_admin: false
    POST: 
      - "/projects/*/logs"    # Can POST to any project's logs
      - "/webhooks/*"         # Can POST to webhook endpoints
      - "/_/ntfy"            # Can send notifications
    GET: 
      - "/projects/myapp/**"  # Can GET from all myapp paths
      - "!/projects/myapp/secret/**" # But not from secret paths
    huproxy:
      - "*.example.com:*"    # Can access specific hosts via HuProxy
      - "localhost:*"
      
  "readonly_token":
    is_admin: false
    POST: []               # No POST access
    GET: 
      - "*"               # Can GET from all paths
      
  "admin_token":
    is_admin: true
    POST: ["*"]
    GET: ["*"]

  # Public token allows unauthenticated access to specific endpoints
  "public":
    is_admin: false
    GET:
      - "/status"          # Allow public status checks
      - "/health"          # Allow public health checks
    POST: []               # No public POST access

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

Use glob patterns to control access: * matches everything, empty array [] denies access, and negation patterns like !secret/** can exclude specific paths.

Public Token Behavior: When no token is provided, the request is treated as using the "public" token. This allows users to create public endpoints within their namespace that can be accessed without authentication.

Example token usage:

# Authenticated access
curl http://patch.tionis.dev/u/alice/projects/logs?token=my_token_name -d "Deploy completed"

# Public access (no token needed, uses "public" token)
curl http://patch.tionis.dev/u/alice/status

Notification System

Send notifications via the /_/ntfy endpoint:

# JSON notification
curl -X POST "http://patch.tionis.dev/u/alice/_/ntfy" \
  -H "Authorization: Bearer my_token_name" \
  -H "Content-Type: application/json" \
  -d '{"type": "markdown", "title": "Alert", "message": "Something **important** happened!"}'

# Plain text notification  
curl -X POST "http://patch.tionis.dev/u/alice/_/ntfy" \
  -H "Authorization: Bearer my_token_name" \
  -H "Content-Type: text/plain" \
  -d "Server maintenance completed"

Monitoring and Metrics

Patchwork provides comprehensive monitoring capabilities through Prometheus-compatible metrics:

Metrics Endpoint

The /metrics endpoint exposes server metrics with built-in authentication:

Authentication

Usage Examples

# Local monitoring (no auth needed)
curl http://patch.tionis.dev/metrics

# Remote monitoring with authentication
curl -H "Authorization: Bearer your-forgejo-token" http://patch.tionis.dev/metrics

# Using query parameter
curl http://patch.tionis.dev/metrics?token=your-forgejo-token

Available Metrics

Health Checks

Standard health check endpoints for service monitoring:

# Health check endpoints
curl http://patch.tionis.dev/healthz
curl http://patch.tionis.dev/status

Modes

Each endpoint supports multiple modes:

Examples

Namespace Behavior Examples

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

# Producer waits until consumer connects
curl http://patch.tionis.dev/public/queue/jobs -d "encode-video.mp4"

# Consumer receives the job
curl http://patch.tionis.dev/public/queue/jobs

Pubsub behavior (broadcast, non-blocking):

# Producer sends immediately to all consumers
curl http://patch.tionis.dev/public/pubsub/events -d "user-login:alice"

# Multiple consumers can listen
curl http://patch.tionis.dev/public/pubsub/events  # Logger service
curl http://patch.tionis.dev/public/pubsub/events  # Analytics service

Flexible behavior with override:

# Default blocking behavior
curl http://patch.tionis.dev/public/./alerts -d "server-down"

# Override to pubsub
curl http://patch.tionis.dev/public/./alerts?pubsub=true -d "system-update"

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" \
  http://patch.tionis.dev/public/queue/api

# Receive with original context
curl -v http://patch.tionis.dev/public/queue/api
# Headers include: Client-IP: 10.0.1.5, Trace-ID: req-12345

Request-responder pattern:

# Responder waits for API requests
curl http://patch.tionis.dev/public/res/api/users

# Requester sends GET request (blocks until response)
curl http://patch.tionis.dev/public/req/api/users

# POST request with data and headers
curl -X POST \
  -H "Authorization: Bearer token123" \
  -d '{"name":"Alice"}' \
  http://patch.tionis.dev/public/req/api/users

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

# Double clutch mode for multiple requests
curl "http://patch.tionis.dev/public/res/api/process?switch=true"

File Sharing

Sending a file:

curl -X POST --data-binary "@test.txt" http://patch.tionis.dev/public/queue/files

Receiving a file:

curl http://patch.tionis.dev/public/queue/files > test.txt

Desktop Notifications (Linux)

#!/bin/bash
MAGIC="notify"
URL="http://patch.tionis.dev/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 http://patch.tionis.dev/p/jobs -d $filename
done

Processing jobs from the queue:

#!/bin/bash
while true
do
  filename=$(curl -s http://patch.tionis.dev/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:

# Get a new channel and secret
curl http://patch.tionis.dev/h
# Returns: {"channel":"abc123-def456-...","secret":"sha256hash..."}

# Send notification (requires secret)
curl http://patch.tionis.dev/h/abc123-def456-...?secret=sha256hash... -d "Server is down!"

# Anyone can listen for notifications
curl http://patch.tionis.dev/h/abc123-def456-...

Reverse Hook Example

Similarly, for reverse hooks, obtain a channel and secret:

# Get a new channel and secret
curl http://patch.tionis.dev/r
# Returns: {"channel":"xyz789-abc123-...","secret":"sha256hash..."}

# Anyone can submit metrics
curl http://patch.tionis.dev/r/xyz789-abc123-... -d "cpu:85%"
curl http://patch.tionis.dev/r/xyz789-abc123-... -d "memory:67%"

# Reading requires secret
curl http://patch.tionis.dev/r/xyz789-abc123-...?secret=sha256hash...

Note: The secrets are generated using HMAC-SHA256 with a server secret key and the channel name. Set the SECRET_KEY environment variable to ensure secrets persist across server restarts.

SSH over WebSocket Tunneling

The huproxy endpoint allows you to tunnel SSH and other TCP protocols through HTTP/HTTPS, useful when direct connections are blocked by firewalls.

Setup

Configure your tokens in your .patchwork/config.yaml file:

tokens:
  "your_secure_token":
    huproxy:
      - "*"  # Allow all hosts and ports
      - "*.example.com:*"  # Or restrict to specific patterns

Usage

# Using with SSH and a WebSocket client
ssh -o 'ProxyCommand=huproxyclient -auth=Bearer:your_token ws://patch.tionis.dev/huproxy/alice/targethost/22' user@targethost

# Testing connectivity
curl -H "Authorization: Bearer your_token" \
     --http1.1 \
     --upgrade websocket \
     http://patch.tionis.dev/huproxy/alice/localhost/22

Tools

You can download a bash based client here.