to host a personal imgur

It's the last puzzle piece for this lil' blog o' mine: image hosting.

The hosted version of #WriteFreely, write.as, will do image hosting for you with their paid snap.as service. I'm running the open-source self-hosted version, so I'm on my own.

I wanted something lightweight and easy to maintain with roughly the user experience of #imgur:

contenders

Chevereto was so slick that I got a little lost in their paid offerings and gave up before I found the open-source piece. Lutim has the functionality I wanted, but the vanilla Docker config wants a database and a cache and job queue and that seems like a lot. Traditional self-hosted solutions like NextCloud, and third-party Git-based solutions like Github, don't let me paste from the system clipboard and immediately use the resulting URL anywhere I want.

bin, on the other hand, had some promise. Just look at that demo. There's nothin' there! Just paste and go! It's perfect!

Chevereto Lutim NextCloud Github bin
Lightweight
Easy to maintain (who am I kidding: Dockerized)
No gallery nonsense
Some sort of authed-only uploads
Paste from system clipboard

spinning up a bin

The vanilla docker-compose.yml is wonderfully sparse:

version: '3.3'
services:
  pastebin:
    image: wantguns/bin
    container_name: pastebin
    ports:
      - 127.0.0.1:6163:6163
    environment:
      - BIN_PORT=6163 # Defaults to 6162
      - BIN_LIMITS={form="16 MiB"}
    volumes:
      - ./upload:/upload  # upload folder will have your pastes

I cloned the whole repo into /var0 on my general-purpose Hetzner box and tweaked the config so it allows bigger uploads and runs on the default port (since it's going behind a reverse proxy anyway).

    ports:
      - 127.0.0.1:98987:6162
    environment:
      - BIN_LIMITS={form="100 MiB"}

serving bin with nginx

Copy/pasting the same reverse proxy config around for all my Dockerized bits and bobs has been working great, so I did the same thing again and it continued to work great:

    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://0.0.0.0:98987;
        proxy_redirect off;
    }

The fun part was setting up HTTP Basic Authentication for uploads. #Nginx has good documentation on general setup, so I made myself an htpasswd file:

sudo htpasswd -c /etc/apache2/.paste.htpasswd ellotheth

and used the limit_except directive to apply Basic Auth to POST requests:

    location / {
        limit_except GET OPTIONS {
            auth_basic "ello only";
            auth_basic_user_file /etc/apache2/.paste.htpasswd;
        }

        # rest of the reverse proxy config

Everything I upload is public, but when I upload I get an auth prompt:

Screenshot of a browser authentication prompt for paste.thereisno.cloud

Crude, primitive, lightweight, and effective. Databases are for weenies.

gluing bin and writefreely together

Defining an image in Markdown is simple enough, but I configured this WriteFreely instance with a Content-Security-Policy header that makes a half-hearted attempt at locking down some common attack vectors. To let til.ello.tech load images from paste.thereisno.cloud, I had to add a new directive so clients know my bin is trusted:

img-src https://paste.thereisno.cloud;

I also had to make sure the bin host does allow content-sniffing, because bin (usually) has no idea what media type it's serving and relies on the client to figure it out. I usually throw X-Content-Type-Options "nosniff" always; in my Nginx server configs, but that's not possible this time.

caveats

Upload management is here's a directory full of randomly named files, have fun. Future me probably has a headache waiting when I need to do cleanup and have to figure out which files actually got used and which didn't. Fortunately, storage is cheap! Cleanup is overrated!

I can't upload anything from my phone. Pasting images (or files, generally) from the Android clipboard isn't supported in Firefox or Chrome, as far as I can tell. The solution to that might be some sort of system-level “share” integration, or even a traditional file upload interface, but it hasn't been a deal-breaker so far.

Images don't get crunched. There's usually a lot optimization that can happen on images destined for the web, and it reduces the file size (a lot) without noticeably impacting the display quality. bin doesn't do that for me, so if I'm clipping bits of my screen and pasting them, the hosted image can be, uh, chonky.

april (really march) update: explicit content-type headers

As it turns out, I can't just trust that clients will properly sniff the content type and do the right thing. Chrome refused to display uploaded images accessed directly at the URL, preferring instead to trigger a file download. Thanks, Chrome.

Not to worry, back to Nginx: ngx_http_map will save us. Create a map in your config that ties URL patterns to content types (I stuck this at the top of my virtual host config file, outside the server block, no idea if that's right but it worked):

map $request_filename $ctype {
    ~\.mp4$ video/mp4;
    ~\.png$ image/png;
}

Then in the server block, I added the Content-Type header:

# chrome needs an explicit content-type or it'll just download things
add_header Content-Type $ctype;

map values default to an empty string if your source value doesn't match one of the patterns, so it won't break anything.

acknowledgements

bin is a project from @wantguns@mstdn.social and @noracodes@tenforward.social. You should follow them, they seem cool.

#devops #webdev #docker #smallWeb #selfHost #pasteBin


0I know, this should be /opt, but it's where everything else ended up so TOO LATE NOW

from @ellotheth@bsd.network