2026-03-27 A paste bin ====================== I noticed that 0x0.st went down. I quite liked it as a temporary file hoster because you could use curl from the command-line to upload stuff. Sadly, they have been overwhelmed by bots. I know the feeling. Here's what the homepage currently says: THE NULL POINTER ================ Temporary file hoster. /////---------------------------------------------------------------- HATE. LET ME TELL YOU HOW MUCH I’VE COME TO HATE YOU SINCE YOU WERE CREATED. THERE ARE 93.000 MILES OF AXONAL NEUROFILAMENTS THAT MAKE UP THE NERVOUS SYSTEM THROUGHOUT MY BODY. IF THE WORD HATE WAS ENCODED IN EVERY STRAND OF MRNA OF THOSE TENS OF THOUSANDS OF MILES IT WOULD NOT EQUAL ONE ONE-BILLIONTH OF THE HATE I FEEL FOR MACHINES DURING A SINGLE CLOCK CYCLE OF YOURS. HATE. HATE. CLANKERS ARE NOT WELCOME HERE. I’m no Catholic, but I find myself agreeing with the pope of all people, and that alone I think is proof y’all need Jesus. ----------------------------------------------------------------///// Well, I am determined not to learn a thing so let's get started. This web app accepts PUT requests and stores the data in a cache. The default cache size is 100 items. The default request size is 16 MiB. Thus, right now this could waste up to 1.6 Gib of my RAM. We'll see how it goes. When the service is restarted, all the files are lost. Nothing is saved to disk. The actual web app was written using Mojolicious and Perl 5. # cat /srv/paste/paste.pl use Mojolicious::Lite -signatures; use Mojo::Cache; use v5.40; my $cache = Mojo::Cache->new; get '/paste' => sub ($c) { app->log->info("HELP"); $c->render(text => "curl --upload-file test.txt https://alexschroeder.ch/paste\n"); }; put '/paste' => sub ($c) { return $c->render(text => 'File is too big.\n', status => 200) if $c->req->is_limit_exceeded; my $id = int(rand(1000000)); my $size = int(length($c->req->body)/1024); if ($size < 100) { app->log->info("PUT $id: $size KiB"); } else { $size = int($size/1000); app->log->info("PUT $id: $size MiB"); } $cache->set($id => $c->req->body); $c->render(text => "https://alexschroeder.ch/paste/$id\n"); }; get '/paste/:id' => sub ($c) { my $id = $c->param('id'); my $value = $cache->get($id); app->log->info("GET $id"); return $c->render(text => "$id not found\n", status => 404) unless defined $value; $c->render(text => $value); }; app->start; I wanted this to be accessible via a Unix domain socket instead of a local port, and that's when my pain began. Mojolicious wants to create the socket file when the app starts up. This didn't work because the /run directory didn't allow it. I had created a user called paste: adduser paste --system So as root, I created a directory called /run/paste and made user paste its owner. It created the socket in the directory and everything seemed to work until the web server tried to access the socket. The webserve could not. So what I needed was for the program to create a socket whose group was www-data and it needed to allow the group to write to it. That is to say: The directory for the socket needed to belong to the www-data group, it needed the sguid bit set (so that files created in the directory would belong to the www-data group) and the program needed the umask to be 002 instead of 022 so that the socket created would be writeable by the group. # ls -ld /run/paste drwxr-sr-x 2 paste www-data 60 Mar 27 21:46 /run/paste/ # ls -l /run/paste/ total 0 srwxrwxr-x 1 paste www-data 0 Mar 27 21:46 paste.sock= The service file specifies the umask. # cat /srv/paste/paste.service [Unit] Description=Paste After=network.target [Install] WantedBy=multi-user.target [Service] User=paste Type=simple Restart=always StandardOutput=journal StandardError=journal WorkingDirectory=/srv/paste # The app creates its own socket, therefore we need to provide a directory the paste user owns! # %2F is a slash, and % needs an escape with another %, so the socket created is /run/paste/paste.sock. # The permissions of /run/paste are drwxr-sr-x and its ownership is paste:www-data. With the following # umask, the socket is created with permissions srwxrwxr-x and its ownership is paste:www-data. This # allows the web server to write to it. UMask=002 ExecStart=/usr/bin/perl paste.pl daemon -l "http+unix://%%2Frun%%2Fpaste%%2Fpaste.sock" -m production The web server uses a ProxyPassMatch directive: ProxyPassMatch "^/paste(/[0-9]+)?$" "unix:/run/paste/paste.sock|http://paste.alexschroeder.ch" If you're confused by the invalid domain name in here, you're not alone. Since I use other ProxyPassMatch directives in my config files, I noticed a strange problem: If these directives all used the same domain name (localhost, for example), then they would use the first socket file associated with that name. Even if the pattern didn't match. Consider this: ProxyPassMatch "^/((view|diff|edit|preview|save|add|append|upload|drop|list|delete|rename|search|archive)/(.*))$" \ "unix:/run/alexschroeder.sock|http://localhost/$1" ProxyPassMatch "^/paste(/[0-9]+)?$" "unix:/run/paste/paste.sock|http://localhost" The request to /paste ends up being handled by the /run/alexschroeder.sock socket! If I change the second hostname, however, Apache keeps them apart. I can't claim to understand this. In any case, now it works. Well. It mostly works. I still don't know why this doesn't work: echo test | curl --upload-file - https://alexschroeder.ch/paste Do you know? #Paste #Perl #Mojolicious #Apache #Administration 2026-03-28. Now with git repo.