IRCv3 FILEHOST extension adapter to external file upload services.
Convoyeur is designed to sit behind the bouncer or server that implements FILEHOST, and proxy upload requests to other places. It reads several headers to determine how to route an upload request:
Soju-UsernameorX-Username: username to look up in the user-to-host mappingX-Upload-Host: the identifier of an upload host
Convoyeur uses these headers in fallback-style logic:
- Use
X-Upload-Hostto select an upload host directly - Use
Soju-Usernameand find the matching upload host - Use
X-Usernameand find the matching upload host - If no username or upload host is given, or there is no matching upload host found, the default upload host is used (if it is defined)
Convoyeur also uses the Content-Type, Content-Disposition (filename parameter), and Content-Length
headers in accordance with the FILEHOST specification.
- Install a Rust toolchain, either via your distribution package manager or with rustup
cargo build --release- The binary will be at
./target/release/convoyeur
Statically-linked binaries are available for x86_64 and aarch64 and attached as assets to the latest release.
OCI (docker, podman, etc) containers are available for linux/amd64 and linux/arm64 platforms on the Github Container Registry.
By default, they expose port 8069 and look for a config file at /config.hcl.
For example:
docker run -p 8069:8069 -v ./config.hcl:/config.hcl ghcr.io/classabbyamp/convoyeur:latest
Convoyeur is configured via an HCL
file. This is passed to the program via the environment variable CONVOYEUR_CONF=path/to/config.hcl.
Logging can be configured via the RUST_LOG
environment variable (default: RUST_LOG=info).
The default configuration includes no configured hosts or users, so it is probably not very useful.
bind = "localhost:8069"
default_host = null
upload_limit = 25A more useful configuration might be:
# IP/hostname and port to run the adapter on.
# to listen on both v4 and v6, use a domain that resolves to both, like localhost
bind = "localhost:8069"
# default host to use for users not listed in the users block
default_host = "0x0.st"
# maximum file size (mebibytes)
# note that some sites may have lower upload limits
upload_limit = 100
# enable stripping of EXIF metadata from photos (supported for PNG, JPEG, JXL, TIFF, and WebP)
# warning: may increase RAM usage, especially with high upload limits and many users
strip_exif = false
# mapping of username to file host ID
users {
foo = "mypaste"
bar = null
}
# host id is given as the block label
host "mypaste" {
# kind of upload, options:
# - form: multipart form
kind = "form"
# url to send uploads to
url = "https://paste.example.com"
# for kind="form", the form field to use for the file
file_field = "file"
# additional fields to send (field_name = contents)
fields = {
extra_value = "24"
}
# extra headers to add to the request (header_name = contents)
headers = {
Authorization = "myS3cr3tT0k3n"
}
}host "0x0.st" {
kind = "form"
url = "https://0x0.st"
file_field = "file"
fields = {
# optional
expires = "24"
secret = "yes"
}
}host "x0.at" {
kind = "form"
url = "https://x0.at"
file_field = "file"
fields = {
id_length = "10"
}
}- logpaste (examples: logpaste.com, paste.gentoo.zip)
host "logpaste" {
kind = "form"
url = "https://logpaste.com"
file_field = "_"
}host "rustypaste" {
kind = "form"
url = "https://rpaste.example.com"
file_field = "file"
fields = {
expire = "1d"
}
headers = {
Authorization = "myS3cr3tT0k3n"
}
}
To use this with the soju bouncer, add the following to your soju configuration:
- a
listendirective for HTTP (https://,http+unix://, orhttp+insecure://), for clients to upload to - an
http-ingressdirective for the internet-facing URL on which the HTTP(S) listener is exposed - a
file-uploaddirective pointing at convoyeur
For example:
listen https://:6680
http-ingress https://soju.example.com
file-upload http http://127.0.0.1:8069/upload
Copyright (c) 2024-2025 classabbyamp
This program is released under the terms of the Québec Free and Open-Source Licence - Permissive (LiLiQ-P), version 1.1.
See LICENCE for full licence text (Français / English).