Skip to content

Blog

Correct permissions for SSH public key auth

When setting up pubkey auth for a user I don't normally SSH with, I was getting this error in /var/log/auth.log (FreeBSD 14.2):

Apr 13 10:08:23 lernie sshd[30096]: Authentication refused: bad ownership or modes for directory /array/media

Most of the information I could find about this error was about (as you may expect) ownership or modes on the authorized_keys file or the ~/.ssh folder. But, as the error suggests, SSH was actually complaining about the modes on the user's home directory, not either of those first two locations.

Reading comprehension is hard, y'all.

SSH will also throw this error if the user's home directory is either group- or world-writable. Unsure why, if the .ssh directory itself has appropriately-restrictive security, but oh well.

So, in summary (running as the jellyfin user being SSH-ed into):

  • chmod 0700 ~/.ssh
  • chmod 0600 ~/.ssh/authorized_keys
  • chmod 0755 ~

Additionally, chown jellyfin:jellyfin for each of these locations.

Credit to kaledev and Arwen on the TrueNAS forums for the fix!

Starting a new Jellyfin plugin

My latest hyperfixation has been organizing my media collection with Jellyfin, which is a fork of Emby, a Plex alternative which went closed-source around 2017.

Jellyfin is less popular than Plex, but has been gaining traction (especially as other media tools pick up support for it). I'm personally using it because it's written in C# and I wanted a system which I could hack on in the future.

The problem

Most of my collection consists of Blu-Ray rips, but I also have a few recordings of sportsball games (acquired legally, I assure you). These don't fit very cleanly into Jellyfin for a few reasons:

No metadata for games

This is the big one. The auto-generated images for these are usually just random still images from some point in the game, which isn't super useful (and frequently are just the "Commercial Break" title screen). Being able to see the team logos would be nice, at a minimum. Navigating to events by team or home/away status would also be nice, similar to viewing media by studio, actor, genre, etc.

Additionally, part of the appeal of media library systems is that they pull this metadata from sources like TheTVDB automatically, based on filenames. This is currently impossible for sports in Jellyfin, unless you have a particularly good naming scheme and directory structure (which presents a very small extensibility surface for automation).

Unclear hierarchy

Movies are easy. TV shows are less easy, but can still be grouped into seasons. Sportsball events can too, but do you group them by team? By sport? How do you distinguish between multiple meetings of two teams in the same arena, other than by date? How do you group meetings within larger events, like how Formula One races also contain practices, qualifying sessions, sprint races, sprint qualifying sessions...

Jellyfin has quite a few plugins, both third-party and maintained by the core project, and a lot of extensibility points at which we can try to address some of these problems. I also have a surplus of free time, so I'm starting out on a metadata plugin which can pull images and other data from TheSportsDB, a free sport data provider.

I thought it would be interesting to document some of my work, and my thought process and strategy for getting from a throwaway shower thought to something that works.

What to do and how to do it

The second point of this post is to talk about my usual development process, particularly the design phase. Similar to how I would build out a feature request in my professional life, I have a few initial goals:

  1. Create something that Jellyfin will load and display on the plugins page
  2. Make it F5-able (read: I can press "Debug" and hit breakpoints, without any faffing about and copying DLLs by hand
  3. Have a CI pipeline that can fail out if something gets pushed that only works on my machine

After cribbing quite a bit of code from the AniDB plugin which is conceptually similar to this one, the Git repo for this project has most of these things. I can click Debug in VS Code and hit a breakpoint in my plugin's constructor, the plugin shows up in Jellyfin itself, and there's a build workflow that uses most of the same CI infrastructure that the Jellyfin project maintains for plugin authors.

Admittedly, all of the paths for the VS Code stuff are specific to my laptop and the other Jellyfin repos I've cloned, but still.

What next

The first actual pieces of functionality I want to implement:

  1. Add a "TheSportsDB Event ID" metadata identity that can store the corresponding event ID in the TheSportsDB API
  2. Add a "Sporting Event" library/media type to Jellyfin. Might be unnecessary or more appropriate for a separate plugin.

I also want to explore Jellyfin's abstractions to see what other improvements I could come up with.

Conclusion

Hopefully this is helpful to others! I'll keep writing as I go along, though I have a nasty habit of starting pet projects then discarding them a few months later. I suppose that's also something I can write about.

Lo-Fi MkDocs blogs using vanilla Git

I made the slug for this "hello-world" since it's my first post, but this is really documenting how I set up Git to automatically publish this blog on my home webserver.

I wanted my blog to be more 2000s and less like a plug-and-play GitHub Pages blog. I have an old motherboard and CPU on my desk which serves, aha, as my "server," so I'm self-hosting it. I didn't want to go to the trouble of setting up a Git web daemon or anything that involved more than my existing Nginx/DNS setup.

I created a Git repo on my server, then cloned it to my desktop over SSH:

katie@juno:~$ git clone katie@rocinante:src/blog

where rocinante is my server and src/blog is the path to the repo, relative to $HOME (but you can also use an absolute path with a leading slash).

Then I built out the MkDocs site locally and committed it.

Now, server-side, we create a Git push hook:

# goes in .git/hooks/post-receive in the repo (server side, obvi)
#!/bin/sh

unset GIT_DIR
cd ~/src/blog
mkdocs build -d ~/www/blog

where ~/www/blog is the directory which Nginx (or whatever) is configured to serve from. The cd command works around MkDocs not supporting builds outside of the same directory as mkdocs.yml.

I also found that I had to add the unset or the RSS plugin would blow up: $GIT_DIR is set by Git itself as part of the hook mechanism, but Git (as invoked by MkDocs) uses that to locate the repo's .git directory if it's set, so unsetting it allows the plugin to read each post's "Last Updated" date.

Oh, and chmod +x it.

And then I write a post, commit it, and (ignore the force push):

katie@juno:~/src/blog$ git push -f
Enumerating objects: 8, done.
Counting objects: 100% (8/8), done.
Delta compression using up to 16 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (5/5), 1.33 KiB | 1.33 MiB/s, done.
Total 5 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
remote: INFO    -  Cleaning site directory
remote: INFO    -  Building documentation to directory: /home/katie/www/blog
remote: INFO    -  Documentation built in 0.65 seconds
To rocinante:src/blog
 + e0cdccd...d4e02c4 main -> main (forced update)

The lines starting with remote: are the mkdocs build output, so you can see the results of the publish (which is how I originally discovered the $GIT_DIR bug).

And that's it!