CrafterBin - A Temporary File Sharing Service in Common Lisp

What is CrafterBin?

CrafterBin is a self-hosted temporary file sharing service written in Common Lisp. It is inspired by 0x0.st and is live at crafterbin.glennstack.dev.

You upload a file (or paste text), get a short URL back, and the file sticks around for a while based on its size. Small files live longer, large files expire sooner. There is no account creation.sign-up requirement-you only need curl and a URL.

Why Common Lisp?

This project started life as nndiscourse, a Gnus backend for Discourse forums written in Emacs Lisp with a Ruby/Python toolchain. That project was ambitious but ultimately unwieldy - the combination of Cask, rbenv, and multiple language runtimes made it fragile and difficult to deploy. It also only worked with public Discourse instances (no login support), which limited its usefulness.

CrafterBin is a complete rewrite that takes a different approach. Instead of trying to bridge Emacs into an external service through layers of glue code, the server side is written entirely in Common Lisp. It compiles down to a single binary via SBCL and runs as a straightforward systemd service. Less dependencies and just a single Lisp image.

The server is built on:

  • Hunchentoot - HTTP server
  • Ironclad - cryptographic tokens and ID generation
  • Bordeaux Threads - background cleanup sweeps
  • unix-opts - CLI argument parsing

The full source is at parenworks/crafterbin on GitHub.

How it works

Upload a file and get a short URL:

curl -F'file=@screenshot.png' https://crafterbin.glennstack.dev

The response is a URL like https://crafterbin.glennstack.dev/Ab3x that you can share.

Retention curve

Files are retained based on a cubic retention curve:

retention = min_age + (max_age - min_age) * (1 - file_size / max_size)^3

With the defaults (7-day minimum, 365-day maximum, 512 MiB max), a 1 KiB text file lives nearly a year while a 256 MiB upload lives about 50 days. A background thread periodically sweeps and removes expired files.

Features

  • File upload via HTTP POST (multipart/form-data)
  • URL fetch - store a remote file by providing its URL
  • Secret URLs - longer, harder-to-guess IDs
  • Custom expiry - set a specific lifetime in hours
  • Management tokens - delete or update expiry via the X-Token response header
  • Magic-byte content-type detection - correctly serves images and other binary files regardless of what the upload client declares
  • Custom filenames - append /filename.ext to any URL to control the download name

More examples

# Upload from a URL
curl -F'url=http://example.com/image.jpg' https://crafterbin.glennstack.dev

# Secret (longer) URL
curl -F'file=@yourfile.png' -Fsecret= https://crafterbin.glennstack.dev

# Set expiry to 24 hours
curl -F'file=@yourfile.png' -Fexpires=24 https://crafterbin.glennstack.dev

# Delete a file (use the token from the X-Token response header)
curl -Ftoken=TOKEN -Fdelete= https://crafterbin.glennstack.dev/ID

Emacs integration - crafterbin.el

For Emacs users, there is crafterbin.el - a small package that lets you upload files and text regions directly from Emacs.

Installation

(use-package crafterbin
  :load-path "/path/to/crafterbin.el"
  :commands (crafterbin-upload crafterbin-upload-file crafterbin-upload-region))

Usage

  • M-x crafterbin-upload - uploads the active region, or prompts for a file if no region is selected
  • M-x crafterbin-upload-file - prompts for a file to upload
  • M-x crafterbin-upload-region - uploads the selected region

On success, the URL is copied to your kill ring and displayed in the minibuffer. Bind it to something convenient:

(global-set-key (kbd "C-c C-p") 'crafterbin-upload)

The package uses curl under the hood and has no dependencies beyond Emacs 24.3+.

Customization

Point it at your own instance if you are self-hosting:

(setq crafterbin-url "https://your-instance.example.com")

Deployment

CrafterBin compiles to a single SBCL binary:

make
sudo make install

It runs as a systemd service behind a reverse proxy (nginx, caddy, etc). All state lives on disk in a storage directory - no database required. File metadata is stored as s-expressions alongside the uploaded files.

Summary

CrafterBin replaces the complexity of the old nndiscourse approach with something simple and focused. The server is a single Common Lisp binary, the Emacs client is a single .el file, and the whole thing is held together by curl. If you need a quick way to share files or text snippets - especially from Emacs - give it a try.

3 Likes

Glenneth, great addition to the systemcrafters infrastructure. I was just looking around for a file upload service to replace 0x0.

I recently found that use-package has a :vc (:url ...) attribute to fetch the upstream library. At least with elpaca this works transparently without needing the custom elpaca way.

(use-package crafterbin
  :vc (:url "https://github.com/glenneth1/crafterbin.el.git")
  :commands (crafterbin-upload crafterbin-upload-file crafterbin-upload-region))

I should probably get in the habit of commit locking my dependencies with all the supply chain attacks, but to manage that I’ll have to craft some tooling.

3 Likes