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.
- 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
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.
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.
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"
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
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.
The server is organized by namespaces with different access patterns. Each namespace now supports structured sub-paths that determine the communication behavior:
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)
/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)
- Example:
/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 viaAuthorization
header.
Patchwork supports behavior determination based on the path structure, providing more predictable and explicit communication patterns:
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
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
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"
Patchwork supports passthrough headers using the Patch-H-*
prefix system, allowing you to forward original request context between clients through the relay system.
- 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 toPatch-H-*
format
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.
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.
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
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
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 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"
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!"
}'
- Create
.patchwork
repository: Each user/organization creates a repository named.patchwork
- Add
config.yaml
: Place the unified configuration in a file namedconfig.yaml
in the repository root - Grant access: Give the special
patchwork
user read access to the.patchwork
repository - Caching: The patchwork server pulls and caches these configuration files as needed
Example repository structure:
user/.patchwork/
├── config.yaml
└── README.md (optional)
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
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
#!/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
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
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-...
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...
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
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
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
git clone https://github.com/tionis/patchwork.git
cd patchwork
go build -o patchwork .
./patchwork start --port 8080
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)
To enable user namespaces with ACL control:
- Set up Forgejo/Gitea instance: Ensure you have a running Forgejo or Gitea server
- Create patchwork user: Create a dedicated
patchwork
user account on your Forgejo/Gitea instance - Generate API token: Create an API token for the
patchwork
user - Configure environment: Set
FORGEJO_URL
andFORGEJO_TOKEN
environment variables - User setup: Users create
.patchwork
repositories and grant read access to thepatchwork
user
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
The /metrics
endpoint provides Prometheus-compatible metrics for monitoring server performance, request patterns, and system health. The endpoint includes comprehensive security controls:
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 inAuthorization
header - Query Parameter: Token can also be passed as
?token=<token>
query parameter
# 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
patchwork_http_requests_total
: Total number of HTTP requests by method, namespace, and status codepatchwork_http_request_duration_seconds
: HTTP request duration histogramspatchwork_channels_total
: Current number of active channelspatchwork_active_connections
: Number of active WebSocket/long-polling connectionspatchwork_messages_total
: Total messages processed by namespace and behaviorpatchwork_message_size_bytes
: Message size histogramspatchwork_auth_requests_total
: Authentication attempts by resultpatchwork_cache_operations_total
: Cache hit/miss statistics
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.
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.
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.
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"
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
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 viaAuthorization
header. Tokens are managed through thehuproxy
field in the user'sconfig.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.
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
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
- Create
.patchwork
repository: Each user/organization creates a repository named.patchwork
- Add
config.yaml
: Place the ACL and HuProxy configuration in a file namedconfig.yaml
in the repository root - Grant access: Give the special
patchwork
user read access to the.patchwork
repository - Caching: The patchwork server pulls and caches these configuration files as needed
Example repository structure:
user/.patchwork/
├── config.yaml
└── README.md (optional)
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
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)
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
#!/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
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
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-...
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).
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
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.
You can download a bash-based client here.
# 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
docker run -p 8080:8080 ghcr.io/tionis/patchwork:latest
git clone https://github.com/tionis/patchwork.git
cd patchwork
go build -o patchwork .
./patchwork start --port 8080
The start
command supports the following options:
--port
: Port to listen on (default: 8080)
Example:
./patchwork start --port 3000
To enable user namespaces with ACL control, configure Patchwork to use a Forgejo or Gitea instance:
-
Set up Forgejo/Gitea instance: Ensure you have a running Forgejo or Gitea server
-
Create patchwork user: Create a dedicated
patchwork
user account on your Forgejo/Gitea instance -
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.
For users who want to use the /u/{username}/
namespace:
- Create
.patchwork
repository in your Forgejo/Gitea account - Add the
patchwork
user as a collaborator with read access - Create
config.yaml
file with your token permissions - Commit and push the configuration
The patchwork server will automatically fetch and cache your ACL configuration when needed.
MIT
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 ./
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