Skip to content
edwardlovealledwardloveall
INTERNET CULTURE AND SOCIAL MEDIA

Ten Years of Hotline Webring

About ten years ago, in early December 2015, I co-created a webring. I’ve been wanting to write a post about Hotline Webring for years now, and the reminder that it’s been a decade gave me the motivation. ### Origins I don’t remember why Gabe and I had this idea, but we do have a penchant for silly projects and puns. Gabe probably came up with some wordplay about Drake’s Hotline Bling song lyrics sounding similar to “webring” and that “lol” turned into “I think we could automatically add sites based on the HTTP Referer header”. Here’s how it works: you make a link on your site that points to https://hotlinewebring.club/____/next (or /prev) and fill in the blank with a unique name that we call a “slug”. When that link is clicked, HLWR notes where it came from (using the referer header) and creates an entry in the webring. Every time someone clicks a link, we find (or create) the site and send them along to the next (or previous) site in the list. It’s not foolproof but it’s about the simplest thing we could think of, and it’s worked for a decade. It’s nice because it’s self-service, and likely one of the reasons the webring has grown so big. ### Growth & members As of this writing, the ’ring has 752 members. Raymond Thomas, the admin of https://brisray.com, keeps a list of webrings both active and inactive. HLWR has the 5th most members of any active webring, which I’m unjustifiably proud of. It started with people we knew. Scrolling to the bottom of our main page, you can see mostly old friends and coworkers. We stopped accepting members for a handful of months because it was overwhelming to have hundreds of sites rely on us like that, and to feel responsible if “something bad” happened on the ’ring. But we re-opened recently and I’ve been able to make my peace with it. It’s a bunch of random sites on the internet that rarely interact with one another. Even the tiniest amount of effort keeps it pretty reasonable and safe. It helps that the members are nice and occasionally write to notify us about questionable sites. Now, most of the ’ring is made up of people I’ve never met or interacted with. Sites with maximalist, GeoCities-inspired sites like (to pick a few at random) wings, ftwr, or FlawedOne. Guest books, shrines, blinkies, button walls, midi files, and visit counters abound. The digital gardens are thriving. I think the most fun is seeing some of the images people create to style the next and previous links: Without any prompting from us people reproduced our style, and many of them do it better. I actually redesigned a new logo nearly 9 years ago, but at this point, white on pink is clearly what we are. ### Moderation As ringleaders, it’s our duty to check every site at least once. We make sure the site: 1. has the next & prev links, 2. isn’t peddling awful content, 3. and is mostly personal (e.g. not a business). The policy has evolved over time to make sure we steer towards individual’s personality (we don’t this to be a marketing/SEO opportunity for businesses), or to account for adult sites (allowed, but you need to communicate the content clearly and have HLWR links before the content). To make it even easier, we use a Slack integration that we lovingly call Sauron. Every site appears via an RSS feed which Sauron sees (presumably with his all-seeing eye). The integration presents actions to approve, unlink, or block: Approve actually does nothing. It’s just a marker for us to communicate to each other “seen it”. Unlink is when we can’t find the links and if they show up again we can give it a second look. Block is used to stop the sites that just don’t fit. Gabe wrote all this and it’s made the whole process much nicer. ### Some tech details The referer strategy works pretty well, but there are some drawbacks. Referers only give you the domain, not the whole path. So if someone put their webring on https://example.com/links/outbound/webrings.html we have to hunt around for a while to find them. It’s usually not too bad but it can be a pain over hundreds of sites. And once we’ve found it, we link people to that page so they have a nicer time surfing the ’ring. Sometimes people don’t send a referer because some well-meaning security advice made them think they needed rel="noreferrer" on all their links. They email us about it and it’s quickly sorted out. We built a tool that tries to scan every website on the ’ring to make sure the links are present, but given the amount of javascript and iframes it doesn’t work that well. I may rebuild it one day to use drive an actual browser, but the low volume of new sites we get now makes it not worth the effort. One thing I added recently is recording the last time someone clicked each link. After a few months of that data, it’s really easy to spot offline sites or ones that no longer our links. To all the other webring admins out there reading, I highly recommend implementing this if you can. It makes pruning old sites so much easier. ### Other random stuff Gabe and I keep accidentally inventing webrings. Like, once we were playing with punycode to put an emoji in a domain name. We both made a page that linked to the others’, and then promptly realized we created another tiny webring. We can’t help it! We sponsored a podcast. We ordered t-shirts and stickers. We’ve distanced ourselves from problematic musicians. We rebranded ourselves from “webring masters” to “ringleaders”. And the ’ring keeps on kickin’ in basically it’s original form. This pun-inspired side project remains silly and nice. I (mostly) don’t care if we’re ever more or less popular than we are now. Thanks to everyone who’s ever added those links to your page or emailed us in good faith. The best thing about Hotline Webring by far is it gives me a reason to keep up with my co-ringleader—my forever internet neighbor and friend—Gabe.
GABERAYMOND THOMAS
S-Config
edwardlovealledwardloveall
BUSINESS AND MANAGEMENT

Companies Could Use A Librarian

Workplaces build up a lot of documentation over time; anything from a annual task to this year’s company goals to staff meeting notes. But how often do you find yourself asking: > Where was that business plan doc? Was it in the Company/Assets folder? Or was it in Shared/Financials? Similarly, this information rarely lives in isolation and often needs to be deduplicated and interlinked. Technically this problem is solved: the web was made for linking documents together. But the effectiveness of that linking is a human problem, not a technical one. This is one of the many things librarians do. They take hordes of information and figure out how to categorize it and make it accessible. We generally think of the information as books, but there’s no reason it couldn’t be technical documentation or company history. Wouldn’t it be amazing to take the skill of library and information science and apply it to your companies Google drive or Confluence docs? Could we make that happen? Could a handful of companies share a part-time librarian? Do consulting librarians exist? I am not a librarian, so I don’t have anything to specifically recommend. I suspect that modern capitalism doesn’t have a lot of room in it’s budget for this kind of less-visible work as it doesn’t directly produce a product. But I think there is value, even to capitalists, in building infrastructure that ultimately makes that work more streamlined and consistent. I also just like fantasizing about a world where the data I need is well-organized and easy to retrieve.
edwardlovealledwardloveall
INTERACTIVE ENTERTAINMENT AND STREAMING

Playing YouTube Anywhere but YouTube

I don’t like YouTube. There’s lots of great people creating many amazing videos there, but the platform itself doesn’t exist to help me or those creators; it’s there to help Google sell ads. So how can I get to the people and their videos, without using the bad parts of the platform? And can I do it in a way that is just as convenient as clicking a link? The answer turns out to be: yes, but it takes some setup. ### What if I download videos and watch them offline? yt-dlp (itself an offshoot of youtube-dl) is a command line tool that you can use to download video from just about any website, including YouTube: yt-dlp "https://www.youtube.com/watch?v=dWqPb16Ox-0" yt-dlp will try to download the “best available quality” version of that video and audio into the current directory. Then you can open it in a video player and watch. This worked for me for a while. I even whipped up a little shell alias so I could copy a video link and type yt in my shell. It would read the URL from the pasteboard and save the video to ~/Downloads. alias yt="pbpaste | yt-dlp --paths ~/Downloads -a -" This is great for archiving, but feels a little heavy for casual watching, especially if it’s a random, unknown video. Doubly so for long videos because you have to download the full thing before you can start to watch it. Or do you? ### Can I stream videos instead? mpv is a “media player for the command line” that plays well with yt-dlp. Pass it a video URL and it will use yt-dlp to fetch and automatically play the video: mpv "https://www.youtube.com/watch?v=iBtTX-CXfcU" It’s highly customizable, but unless you’re into configuring everything just the way you want it the interface is a bit unfamiliar and unpolished. ### How about a native player? Luckily, there are many mpv alternative frontends you can use. I’m on macOS so I chose the venerable IINA. To use youtube-dl with IINA, you’ll need to configure it. If you want to use yt-dlp instead, you’ll additionally have to make a symbolic link from youtube-dl to yt-dlp because IINA looks for youtube-dl specifically: ln -s $(which yt-dlp) "$(dirname $(which yt-dlp))/youtube-dl" Now I can open up IINA, choose Open URL... from the File menu, and paste in a link such as this, and watch it play in a nice, clean UI. But I still have to copy and paste URLs around. Which got me thinking: “Can I get these links to auto-launch in IINA? Does IINA have a custom URL scheme? Surely one of those URL-rewriting browser extensions will let me do this.” The answer to all of that is a delightful: yes! ### Automatically opening videos It’s not documented anywhere I could find, but you can use iina://weblink?url={video url here} URL scheme to launch IINA and play a URL. I used the Redirector extension to redirect everything. Here’s my redirection config: - Include pattern: (https://(www\.)?youtube.com/(shorts|watch).+) - Redirect to: iina://weblink?url=$1 - Pattern type: Regular Expression - Apply to: Main window, HistoryState Any YouTube link I click (like this one) will now open and play in IINA. ### Automatically opening system-wide I also set up a system level redirect using Finicky. The current Finicky version is a rewrite and had some weirdness for me, so I’m on version 3.4.0. Here’s my config that lives at ~/.finicky.js: module.exports = { defaultBrowser: "Waterfox", rewrite: [ { match: /(https:\/\/(www\.)?youtube.com\/(shorts|watch).+)/, url: ({url}) => ({ ...url, protocol: "iina", host: `weblink?url=https://${url.host}` }), } ], handlers: [ { match: /(https:\/\/(www\.)?youtube.com\/(shorts|watch).+)/, browser: "IINA" } ] } Now anything that links to a YouTube video will open and play in IINA. Since IINA uses yt-dlp, it will also play videos from lots of places. Instagram, TikTok, random websites with video embeds, you name it. If you want the auto opening behavior you’ll have to set those up individually, but I generally just paste in the URL manually for the handful of those I come across every month. And there’s definitely some weirdness; videos don’t always load, YouTube is starting to ask users to sign in before watching, sites break and yt-dlp can’t fix them instantly. But overall, I’m really happy to stay off of a Google platform, and it was extremely satisfying to glue all these pieces together.
GOOGLE
edwardlovealledwardloveall
GENERATIVE AI AND FOUNDATION MODELS

Some cool things I learned in the first half of 2025

I learn new things all the time and I keep meaning to write them all all up, but then I get in my own way. I’m not going to justify any of this and stop writing the intro now. ### Easy sudo If you use ZSH, you can map a specific key to output text, and that text can include other commands (or at least line editing actions). One great example of this I saw was adding sudo to command automatically: bindkey -s "^F" '^asudo ^e' This says map the input ^F to go to the beginning of the line (^a) type sudo then move to the end of the line (^e). ### Shell backgrounding I always knew you could put commands in the background, but I never really bothered to learn it. However, I’ve started to really appreciate at least a very basic use case: background one thing with ^Z, then bring it back (foreground it) with fg. I’ve used this quite a bit to keep my place in a long man page, or even a repl like rails c that might take a long time to boot up. ### Ansible pipelining If you use ansible a lot, you may know that every task (a single action in ansible parlance) is uploaded with one connection, and then executed with a second. However, if you turn on ANSIBLE_PIPELINING, it can do all of this over a single connection. This speeds tasks up dramatically. In theory this can speed up ansible by 2x, but it feels more like 3-4x to me. ### Co-op jobs Looking for a job? Interested in co-ops? Checkout apply.coop. See also the comments on lobste.rs which has some other cool job boards. See also also this post on LinkedIn (sorry). ### Self-hosting Interested in self-hosting but overwhelmed by where to start? Check out Yunohost. It’s a whole website/community/technology that (in theory) makes self-hosting easy. ### Artist platform with out generative AI Cara. From their about page > With the widespread use of generative AI, we decided to build a place that filters out gen AI images so that people searching for authentic creatives and images can do so easily. […] we won’t host AI-generated portfolios unless the rampant ethical and data privacy issues around datasets are resolved via regulation. Nice. Found some new favorite artists, too: - Kenze Wee - Oddarette - Stella Bialek - hby ### UI research/inspiration Open UI is doing a lot of work to define and catalog UI patterns. Tons of great research into existing design systems if you need to see a bunch of <select> in one place, or you’re trying to name a thingamajig or a whatsit. ### Worker-owned games journalism I’m sad Polygon is (basically) dead but that means I was forced to find Aftermath. I especially enjoy their Aftermath Hours podcast and choice of merch. ### This old tweet I’m still laughing at days later
KENZE WEESTELLA BIALEK
edwardlovealledwardloveall
EMBEDDED SYSTEMS AND FIRMWARE

Setting Up a Local Debian 12 Server with UTM

I’ve been doing a lot of linux system administration, both at work and as a hobby. This very site is hosted on a server set up by me. I am very proud. The stakes feel high when doing server work. You can easily lock yourself out in a number of different ways, lose data, and destroy all your hard work with the wrong command. I thought it would be nice to have a place to practice so I’ve been using UTM to host a local, virtual server. Specifically, I wanted to emulate my servers as closely as possible which run Debian 12 “bookworm” on Intel processors (x86_64). I used to use Vagrant and VirtualBox for this, but they haven’t been as reliable lately. That, plus it’s fun to try and avoid Oracle. ### Download Stuff UTM can be found on the UTM website. You’ll also need a Debian 12 disk image is on the Debian downloads page. I downloaded the 64-bit PC netinst iso (actual download link (551 MB)) disk image. ### Setup In UTM, create a new VM. Select: - Emulate - Linux - Browse for a Boot ISO Image and select the debian-12.9.0-amd64-netinst.iso downloaded from Debian. The default Hardware options should be fine: - Architecture: x86_64 - System: Standard PC (Q35 + ICH9, 2009) (alias of pc-q35-9.1) (q35) * Future readers: I expect this will get outdated as new processors come along, but it’s unclear. Just pick the default. - Memory: 4096 (I lowered mine to 2048) - CPU: Default number of cores - Don’t check Enable hardware OpenGL Acceleration * I don’t plan to have any graphics here. It’s just a server. - Storage: 64 GiB - No Shared Directory Path Finally, you can give this a name like Debian 12 (x86_64 emulated). ### Install Clicking the ▶️ play button will boot the machine and start the install process. I chose Install not Graphical Install because I am a hacker like I said I don’t plan to use any graphics for the machine. The install process is pretty straight forward. You can use arrow keys or sometimes Tab to move around the options. You pick things like your language, region, root password, user name, user password, etc. One thing that I was unsure about was setting up disk partitioning. I went with Guided - use entire disk, picked SCSI2 something something, and then All files in one partition. It seems to work fine. I also did not select any window manager like GNOME or KDE Plasma. Just the SSH server and standard system utilities. Installing takes quite a while. Dedicate an hour to it. Once it’s done, stop the machine and edit it so no drive points to the install ISO, otherwise it might boot up to the installer again. - Right click VM - Stop - Right click VM - Edit - Drives ➡️ IDE Drive. One of these is your “disk” drive and one is your hard drive. You want the disk drive. - Click Clear next to the ISO path Now you can click ▶️ play to start up again. ### Snapshots Now you have an local install you can use for testing without fear of losing anything, which is great. However, since it takes a while to set up, it could be useful to save that work with a snapshot. First, install qemu cli tools. I used homebrew so a typed this: brew install qemu Then, navigate to the UTM machine in your shell. You can right-click the machine in the main UTM window and select Show in Finder or it’s probably located at: ~/Library/Containers/com.utmapp.UTM/Data/Documents/NAME.utm. It’s actually a bundle directory, so go one level deeper into its Data directory. From there you’ll see a file with a long UUID with the qcow2 extension; e.g. F999E790-1FD2-4FA1-BAC2-488D87D9442B.qcow2. This is your image. To take a snapshot, the machine needs to be stopped, so do that first. Snapshots are managed with the qemu-img command: # Create a snapshot with NAME qemu-img snapshot LONG_UUID.qcow2 -c NAME # Restore (apply) a snapshot by NAME qemu-img snapshot LONG_UUID.qcow2 -a NAME # List snapshots qemu-img snapshot LONG_UUID.qcow2 -l # Delete a snapshot qemu-img snapshot LONG_UUID.qcow2 -d NAME I took a snapshot right after the install so if I messed anything up I could easily start from a known place. ### SSH To set up SSH I had to do two things. First, I logged in as the root user by typing root at the initial boot up screen in UTM and typing the password I set during install. Then, I edited the ssh config at /etc/ssh/sshd_config and changed the following line to allow me to ssh in as root: - #PermitRootLogin prohibit-password + PermitRootLogin yes and restarted the ssh server: systemctl restart ssh Finally, to get the IP address of the machine, I used the ip command: ip -4 -br addr which is something like 192.168.64.xxx Armed with all that, I can ssh into my UTM machine with a password: ssh -o PreferredAuthentications=password root@192.168.64.123 The -o PreferredAuthentications=password part is to ask ssh to let me try a password. For whatever reason it needed this or it would tell me Too many authentication failures. I must have some config somewhere or I misunderstand the ssh defaults. You may not need it. Now you have a machine you can do whatever with. You can test your server automation scripts, or take a new snapshot, or do whatever!