It Was Terrible Even Before It Was Slop, But It “Works”
Keeping Calibre running on FreeBSD
I have been running Calibre for years now as a way to manage my ebook library. I started with the desktop app on my Macs, but with the library itself saved to the cloud so I could access it from different computers. Calibre isn't really meant to be run this way, and you can run into trouble if you make changes from one computer while the app is still active on another computer, so avoiding these issues is key. To get around the problems with its single instance model, three years ago I got Calibre running on my VPS and configured it so that I could upload new books in the web interface and have the application convert them automatically to my preferred format. (I also jailbroke my Kindle and installed KOReader so I can download books directly from the Calibre server without having to sideload them, but that's outside scope for this post). At some point, however, my server quit letting me upload books, popping up a cryptic 500 error and logging a separate cryptic error. This problem seemed to be specific to FreeBSD so I filed a bug against the package and did some poking.
To be honest, I don't really like Calibre. I've looked for a better tool for the job, but unfortunately, despite its various historic issues (huge download size, confusing array of options, interface that I'm not even sure the developer's mother could love), there isn't really anything else yet that you can run for the same purpose and perform the same functions. Worse still, its developer has leaned hard into slop, adding chatbot interactions and a cover image generator, and he's using LLMs to generate code. But I use a couple plugins to redeem ACSM files and remove DRM so my books are actually my books. And because they only work if they're plugged into Calibre, I'm stuck with it.
The bug I encountered seemed connected to Calibre's own daemonize option, which does a double fork in order to detach from the parent process. Something seems to have changed with the way this works on FreeBSD, and while Calibre's main process was running, it was missing a worker process that should also have been forked when the daemon launched. While trying to figure out what was happening I remembered that FreeBSD also provides its own way to daemonize processes, which is (appropriately enough) called daemon(8). In fact, this is a common enough sort of use case that there's a pattern for the rc.d script.
This was one of those problems that I probably could have fixed more quickly if I hadn't been trying to modify the existing rc.d script that comes with the calibre package on FreeBSD, but because I don't have to write rc.d scripts often I made many, many errors while trying to get the syntax right. This solution is a little bit “draw the rest of the fucking owl” so I don't want to use the word simply, but the solution that worked for me was to change just a few lines in one file after I figured out what lines to change and how to change them, and to change one other line in /etc/rc.conf to match the new process.
The primary file to be changed on a live system is /usr/local/etc/rc.d/calibre. If you're looking at this in FreeBSD's ports tree, the file to edit is deskutils/calibre/files/calibre.in.
Here's what to change:
- In the header declaration, change
calibre_flagstocalibre_args. - Find the line that starts
command=(just below the line that startspidfile=), and replacecommandwithcalibre_command. - Below that line, add another line that says
command=/usr/sbin/daemon. - Find the line that starts
command_interpreter=(it should be the next line) and replacecommand_interpreterwithprocname. - Below the line that starts
calibre_env=add two new lines:calibre_flags="-f -o ${calibre_logfile} --"andcommand_args="${calibre_args} --log ${calibre_logfile} --max-log-size ${calibre_logsize} --pidfile ${pidfile}" - Lastly (for this file) there is one last line that starts
command_args=after the twoifblocks, before thecalibre_prestart()command. Replace this line with:command_args="${calibre_command} ${command_args} ${calibre_library}". - Edit
/etc/rc.conf, find the line that startscalibre_flags, and changecalibre_flagstocalibre_args.
For reference I'm putting the whole rc.d file from my working system at the bottom of this post.
The main thing this does is use daemon(8) instead of Calibre's own daemonize option. Command line options passed to daemon(8) have to be set off from the arguments you're passing to the target process, so some of these changes are just there to separate the daemonize arguments (here defined in calibre_flags and terminated with --) from the command line arguments being passed to calibre-server (here defined in command_args). I thought changing calibre_flags to calibre_args in rc.conf helped to make the difference between flags and args readable.
I hope this helps someone else, but even if it just helps future me, he'll be happy.
# PROVIDE: calibre
# REQUIRE: DAEMON
# KEYWORD: shutdown
#
# Add the following lines to /etc/rc.conf to enable calibre_server:
#
# calibre_enable (bool): Set it to "YES" to enable calibre
# Default is "NO".
# calibre_port (int): port that calibre_server listens on
# Default is 8080
# calibre_user (string): user that calibre_server runs as
# Default is calibre
# calibre_home (string): home directory for calibre_server
# Default is the home directory of calibre_user
# calibre_url_prefix (string): prefix to append to all URLs
# default is unset
# calibre_logfile (string): log file location
# Default /var/log/calibre-server/calibre-server.log
# calibre_logsize (int): size of log file before being rotated in MBs
# Default 10 MB
# calibre_args (string): Any further flags to customize configuration
# Default empty
# calibre_library (string): path to library folder to serve content from
#
#
##########################################################
. /etc/rc.subr
export PATH=$PATH:/usr/local/sbin:/usr/local/bin
name=calibre
rcvar=calibre_enable
load_rc_config $name
: ${calibre_enable:=NO}
: ${calibre_user:=calibre}
: ${calibre_library:=/nonexistent}
: ${calibre_logfile:=/var/log/calibre-server/calibre-server.log}
: ${calibre_logsize:=10}
pidfile=/var/run/${name}/${name}.pid
calibre_command=/usr/local/bin/calibre-server
command=/usr/sbin/daemon
procname=/usr/local/bin/python3.12
required_dirs=${calibre_library}
start_precmd=calibre_prestart
calibre_home=${calibre_home:-$(getent passwd ${calibre_user} | awk -F: '{print $6}')}
calibre_env="${calibre_env} HOME=${calibre_home:-/nonexistent}"
calibre_flags="-f -o ${calibre_logfile} --"
command_args="${calibre_args} --log ${calibre_logfile} --max-log-size ${calibre_logsize} --pidfile ${pidfile}"
if [ ! -z "${calibre_port}" ]; then
command_args="${command_args} --port=${calibre_port}"
fi
if [ ! -z "${calibre_url_prefix}" ]; then
command_args="${command_args} --url-prefix=${calibre_url_prefix}"
fi
command_args="${calibre_command} ${command_args} ${calibre_library}"
calibre_prestart()
{
install -d -o ${calibre_user} -m 755 /var/run/${name}
install -d -o ${calibre_user} -m 755 `dirname ${calibre_logfile}`
}
run_rc_command "$1"
I don't have comments on here, but if you want to respond to this post, I'm on the fedi as fedward@distraction.party.