Hywel Andrews
minimal human

June 28th, 2025

Obsidian on FreeBSD with Google Drive

FreeBSD provides a repository for user-space application binaries, which can be managed with pkg tools. It’s a fantastic base of software, and one of the main reasons I choose FreeBSD over say OpenBSD (along with its speed), but there are some caveats; mostly to allow you, the user, the most customisation and to align with the FreeBSD release process.

If you followed Freebsd for your laptop you will have installed the STABLE quarterly release version (as opposed to CURRENT) and therefore are tacked to the quarterly ports repository. This is great for stability, this branch only receives security and hot-fixes and is then sync’d to latest before the release in the next quarter. A desktop system may have essential applications which depend on some of the heaviest and therefore flakiest dependencies in the ports tree however, namely electron. For my development machine, I rely on sway, vscode, chromium, wifimgr, darktable and Qalculate. There have been multiple times when vscode has fallen out of the ports tree, and on the last upgrade to FreeBSD 14.2, with it once again gone (and this time for three months until the next cut from latest) it was time to switch to the latest branch. The updates will come thick and fast, but with careful locking, you can be in a similar position as with quarterly, but always able to fetch fixes and resolutions to fallouts, along with being closer to the latest versions.

The repository configuration file pointing to quarterly is located in /etc/pkg/FreeBSD.conf. Copy this file to /usr/local/etc/pkg/repos/FreeBSD.conf then edit so that it reflects

FreeBSD: {
	url: "pkg+http://pkg.FreeBSD.org/${ABI}/latest",
}

Creating our own FreeBSD.conf means future OS upgrades won’t reset changes made there, as opposed to /etc/FreeBSD.conf. Now update the pkg repository

doas pkg update -f

and then upgrade your packages

doas pkg upgrade


Gunpowder

But we want to go a step further. We can provide a pkg like experience to ports that are not built into the pkg binaries repository. For example, there is currently a port of Obsidian, but it’s excluded from pkg builds due to a EULA; FreeBSD is much stricter than Linux about what it’s users are signing up for (but maybe the use of different repositories in the Linux world serves exactly this purpose, and FreeBSD could follow). First lets install poudriere, a tool used as part of the ports pkg tree infrastructure, handling, build, management and visibility. For our use case, we simply want to provide a local repository with one binary application, obsidian.

doas pkg install poudriere

We are using ZFS, so configure the pool name in /usr/local/etc/poudriere.conf

Next we need a jail to run our builds, they are typically created per major release per architecture:

poudriere jail -c -j 14amd64 -v 14.3-RELEASE

Finally we setup a ports tree

poudriere ports -c -p HEAD -m svn+https

Now we can build our missing port

poudriere bulk -j 14amd64 -p HEAD -b latest -S textproc/obsidian

Here we are building obsidian in our 14.3 jail for amd64 -j 14amd64, using the ports tree attached to the tip of the default branch -p HEAD, and using binary packages from the latest branch -b latest -S and without rebuilding on dependency changes. These last flags are important to build our package on top of binary instead of from source. Although it is preferred to build from source, as the HEAD of ports and latest branch can still differ, in our case building electron34 and rust dependencies would take 2 days and would not succeed (due to resource constraints).

Finally we need to add our local poudriere repo to our local pkg manager

Create the file /usr/local/etc/pkg/repos/poudriere.conf with the below contents (assuming the same jail name)

local: {
  url: "file:///usr/local/poudriere/data/packages/14amd64-HEAD",
  enabled: yes
}

Finally update your repositories

pkg update -u

And you should find obsidien

$ pkg search obsidian
obsidian-1.8.10_4              Powerful and extensible knowledge base application

Where are my files?

I use google drive to sync my obsidian notes between devices (except iOS *shakes fist at the clouds*) so we need a way to have this mounted after login. Our tool of choice is rclone so lets

pkg install rclone

Now we need to configure our google drive api keys, using the conveniently supplied rclone config command, follow the steps as instructed.

Lets now confirm we can connect to our gdrive instance, I choose to place mounts in /media/

mkdir -p /media/gdrive

doas rclone cmount -vvv gdrive: /media/gdrive --allow-other --direct-io --config ~/.config/rclone/rclone.conf --daemon

Here we are using cmount instead of mount as we need to go through user-land FUSE drivers. We increase verbosity in case of errors (-vvv) tell rclone to use the google drive endpoints (gdrive:) and the location of our mount (/media/gdrive), ensure users other than root can access files (--allow-others) and remove any filesystem caching (--direct-io). Finally we specify the config (--config) and run rclone as a background process (--daemon).

Hopefully you have a working gdrive connection and we can move this into a service to be available after login, just like other platforms.

In /usr/local/etc/rc.d create the script rclone_gdrive with the below contents

#!/bin/sh

# PROVIDE: rclone_gdrive
# REQUIRE: LOGIN
# KEYWORD: shutdown

. /etc/rc.subr

name="rclone_gdrive"
rcvar="rclone_gdrive_enable"

load_rc_config $name

: ${rclone_gdrive_enable:="NO"}
: ${rclone_gdrive_config:="/usr/local/etc/rclone/rclone.conf"}
: ${rclone_gdrive_mount_point:="/media/gdrive"}

pidfile="/var/run/${name}.pid"

start_cmd="${name}_start"
stop_cmd="${name}_stop"
status_cmd="${name}_status"

rclone_gdrive_start()
{
    echo "Starting ${name}."

    command="/usr/local/bin/rclone"
    command_args="cmount -vv gdrive: ${rclone_gdrive_mount_point} --allow-other --direct-io --config ${rclone_gdrive_config} --daemon"

    # Create mount point if it doesn't exist
    if [ ! -d "${rclone_gdrive_mount_point}" ]; then
        mkdir -p "${rclone_gdrive_mount_point}"
    fi

    # Check if already mounted
    if mount | grep -q "${rclone_gdrive_mount_point}"; then
        echo "${name} is already mounted at ${rclone_gdrive_mount_point}"
        return 1
    fi

    # Start rclone mount
    ${command} ${command_args}
    # Save pid from child daemon process (instead of parent process using $>!)
    echo $(pgrep -f "rclone.*${rclone_gdrive_mount_point}") > ${pidfile}

    # Wait a moment and check if mount succeeded
    sleep 2
    if mount | grep -q "${rclone_gdrive_mount_point}"; then
        echo "${name} started successfully."
        return 0
    else
        echo "Failed to start ${name}."
        rm -f ${pidfile}
        return 1
    fi
}

rclone_gdrive_stop()
{
    echo "Stopping ${name}."

    # Unmount the filesystem
    if mount | grep -q "${rclone_gdrive_mount_point}"; then
        umount "${rclone_gdrive_mount_point}"
        if [ $? -eq 0 ]; then
            echo "Unmounted ${rclone_gdrive_mount_point}"
        else
            echo "Failed to unmount ${rclone_gdrive_mount_point}, trying force unmount"
            umount -f "${rclone_gdrive_mount_point}"
        fi
    fi

    # Kill the process if it's still running
    if [ -f ${pidfile} ]; then
        pid=$(cat ${pidfile})
        if kill -0 ${pid} 2>/dev/null; then
            kill ${pid}
            sleep 2
            if kill -0 ${pid} 2>/dev/null; then
                kill -9 ${pid}
            fi
        fi
        rm -f ${pidfile}
    fi

    # Kill any remaining rclone processes for this mount
    pkill -f "rclone.*${rclone_gdrive_mount_point}"

    echo "${name} stopped."
}

rclone_gdrive_status()
{
    if [ -f ${pidfile} ]; then
        pid=$(cat ${pidfile})
        if kill -0 ${pid} 2>/dev/null; then
            echo "${name} is running as pid ${pid}."
        else
            echo "${name} is not running but pidfile exists."
            return 1
        fi
    else
        echo "${name} is not running."
        return 1
    fi

    if mount | grep -q "${rclone_gdrive_mount_point}"; then
        echo "Mount point ${rclone_gdrive_mount_point} is active."
    else
        echo "Mount point ${rclone_gdrive_mount_point} is not mounted."
        return 1
    fi
}

run_rc_command "$1"

Add the above service to your rc.conf, along with any parameters that need configuring

sysrc rclone_gdrive_enable=YES

Next we can start our new service

service rclone_gdrive start

Launch obsidian and find our notes.

Screenshot of Obsidian working on FreeBSD


Keeping our local repository updated

To provide updates to pkg we will need to update our poudriere ports tree

poudriere ports -u -p HEAD

And rebuild from our jail as before

poudriere bulk -j 14amd64 -p HEAD -b latest -S textproc/obsidian

At this stage I’m happy with the ease at adding further ports to install alongside regular binaries from latest; moving this to my FreeBSD home server and setting up weekly poudriere repository updates will be the next stage.