Selfhosting Snac2 on Tailscale Funnel
------------------------------------------------------------------
This is a blogpost describing my journey trying to selfhost
snac2[1] via Tailscale funnel[2] on a spare computer. I started the
process in April and finally got the server working yesterday (11th
October 2024) because of stupid mistakes made early on.
=>
https://comam.es/what-is-snac [1]: snac2 software
=>
https://tailscale.com/kb/1223/funnel [2]: Tailscale Funnel
## Quick Background
I have a couple of accounts on the Fediverse: my main Mastodon[3]
on Mastodon.social[4] and a second account on another Mastodon
server[5]. I have been on the Fediverse since 2018 and had 2
accounts for that time, however, the second account has changed
servers a few times because of server closure. For a long time it
was on a Pleroma server, then briefly on an Epicyon server. As
such, I've not become fixed to specifically Mastodon software and
I've looked to experiment with other software. Snac2 is nice simple
Fediverse software that does (mostly) what I want (and is written
in a language I understand), so I decided to try it (I'll go into
"mostly" later). Self-hosting, though, can be expensive with a VPS
and I'm behind a NAT at home. Fortunately, when Tailscale
originally launched their "Funnel" feature, Mastodon was on the
rise in the tech press so they mentioned[6] it could be used "to
host your personal blog or a small Mastodon server on your own
computer". This got it noticed and promoted on the Fediverse, so I
thought I'd try it.
=>
https://joinmastodon.org/ [3]: Mastodon software
=>
https://mastodon.social/@dheadshot [4]: My account on
Mastodon.social
=>
https://topspicy.social/@ddlyh [5]: My other Mastodon account
=>
https://tailscale.com/blog/introducing-tailscale-funnel [6]:
Blogpost introducing Tailscale Funnel
## Starting off...
I decided to use a small HP Mini-PC running Ubuntu so I could set
it up as a home server without it taking much space, yet not having
to leave behind the amd64 architecture and struggle with Arm, like
I would with a Raspberry Pi. I set up a local SSH and installed
Tailscale software (I don't remember much of how I did either of
these as it was months ago, so I'll gloss over it). I then
downloaded the snac2 software with a `git clone`, *but* I had no
intention of running it directly...
## Forking Snac2
Remember that "mostly" from earlier? There are two ways to
interpret the ActivityPub "Like" activity[7] and different
Fediverse software use different interpretations. I call them
"Like" and "Favourite", because Twitter has used both over time and
that's what it referred to them as. Essentially, a "Like" is a
"Favourite" that's also shared with your followers like a
"boost"/"repost"/"repeat"/"reblog" (this other action has lots of
names across the Fediverse, so I'm listing the most common). I
prefer Favourites as, if I want to share a post as well, I'll boost
at the same time. It just makes sense to me to keep the actions
separate. Snac2, however, uses the "Like" interpretation, so I
decided to soft-fork snac2 and make some changes[8].
First change was obviously to make the "Like" action (grouped with
boost and called "Admire" in the sourcecode) not share the post.
This turned out to be remarkably simple[9] and just required
checking in the "Admire" function that the type was an "Announce"
(what the sourcecode calls boosts) before I'd let it be boosted, so
needed one line of code. However, while I was there, I made some
other changes. The Commandline interface to snac has the ability to
boost but not like/favourite a post, so I added[10] that (along
with the reverse of unliking/unfavouriting it). I then set the site
icon and default user display picture to be my own, albeit
converted to monochrome PNG8 with the command:
```
convert Av.png -dither FloydSteinberg -fx '(r+g+b)/3' -colors 2
-colorspace Gray PNG8:monoAv.png
```
I added the Base64 value to the code[11] in place of the default,
though I didn't bother removing the special code for changing the
icon on certain days (I may do in the future: I notice that today
it's different, so I may have to soon). I also changed the version
number to indicate it was a fork[12].
=>
https://www.w3.org/wiki/ActivityPub/Primer/Like_activity [7]:
ActivityPub Like activity primer
=>
https://codeberg.org/dheadshot/snac2-dhfork [8]: snac2-dhfork -
my soft-fork of snac2
=>
https://codeberg.org/dheadshot/snac2-dhfork/commit/587b4b2d388f6d0c0bbd08ae220777bc7895b1e2
[9]: The commit changing the Like action
=>
https://codeberg.org/dheadshot/snac2-dhfork/commit/e9f5f19c7d7c063d9feab5df6936774cb660ebe7
[10]: The commit adding the commandline Like/Favourite action.
=>
https://codeberg.org/dheadshot/snac2-dhfork/commit/aff4364754406bce2853b5e843546b092400a51a
[11]: The commit changing the display-picture and site-icon
=>
https://codeberg.org/dheadshot/snac2-dhfork/commit/7f165ad6f0061d8b1b4035efd532067d3f2bb911
[12]: Commit changing version number. Not really important.
## Continuing...
Having done all that, I compiled and installed it and got to work
setting up the server. I created a "snac" user and created the
snac2 datafolder in its "$HOME" directory (with it having the only
permissions over it). I linked the snac2 certificate settings to
the Tailscale ones (vitally important). I set up NginX[13] to be an
interface between the snac2 server and the funnel, so I could get
all the HTTP headers etc working. I based my configs on the example
for a snac2 systemd daemon[14] and NginX configuration[15]. These
configurations turned out to be a mistake. Here is a redacted
version of my Systemd configuration:
```
[Unit]
Description=A simple, minimalistic ActivityPub instance
Documentation=
https://codeberg.org/grunfink/snac2/src/branch/master/doc
After=network.target
Wants=network-online.target
[Service]
DynamicUser=yes
User=snacuser
Group=snacuser
StateDirectory=/home/snacuser/snacdata
ExecStart=/usr/local/bin/snac httpd $STATE_DIRECTORY
Restart=on-failure
[Install]
WantedBy=multi-user.target
# This is a systemd global service example. Edit and run:
#
# cp snac-global.service /etc/systemd/system/snac.service
# sudo snac init /var/lib/snac
# sudo snac adduser /var/lib/snac USER
# systemctl enable snac
# systemctl start snac
```
DO NOT USE THIS VERSION. It didn't work at all. People who know
Systemd will be screaming at me at this point, but I was bodging a
script from limited knowledge, so I carried on, changing:
```
ExecStart=/usr/local/bin/snac httpd $STATE_DIRECTORY
```
to
```
ExecStart=/usr/local/bin/snac httpd /home/snacuser/snacdata 2>&1
>>/var/log/snaclog.txt
```
This *SEEMED* to work, but had issues: I couldn't seem to control
it from the web interface. The commandline version worked fine
though, so I did some tests.
=>
https://nginx.org/ [13]: NginX
=>
https://codeberg.org/dheadshot/snac2-dhfork/commit/92b3b71532e58f8ee69d2e3e006691bdd469ce76
[14]: Example Systemd Daemon
=>
https://codeberg.org/dheadshot/snac2-dhfork/src/branch/master/examples/nginx-alpine-ssl/default.conf
[15]: Example NginX configuration
## More bugs
I managed to do lots of stuff from the commandline, including
posting, following, liking/favouriting and boosting, but couldn't
set anything up from the web end. I ended up giving up for a while,
revisiting over the coming months and getting more and more
frustrated. I tried many NginX configurations but none helped.
Eventually, I found documentation to get Tailscale Funnel working
directly[16], though it didn't work at first because the format of
the commands had since changed. The main documentation[17] set me
back on track though.
One thing I noticed was that the logs were empty, so I bumped the
debug level up to 3 and tried again. No logs in `/var/log`, but
systemd had some, so I muddled on. After a while, I noticed the
systemd warning that `StateDirectory` was an absolute path, so
changed it to `~snacuser/snacdata` which seemed to fix it (it
didn't though). Eventually, I tried changing `ExecStart` to:
```
ExecStart=/usr/local/bin/snac httpd /home/snacuser/snacdata 2>&1
|tee /var/log/snaclog.txt
```
This didn't work, because Systemd interpretted the `|tee` as an
argument for snac! Frustrated, I wrapped the thing in brackets for
a subshell, only to discover that Systemd doesn't do
shell-expansion stuff. "Aha!", I thought. "This must be why I can't
get logs in `/var/log`!", so I made a bash script to do the piping
to `tee` separately and set `ExecStart` to that.
This gave a different error, but an intriguing one: `tee` couldn't
access `/var/log/`, saying "access denied" for reasons of a
"read-only filesystem". People who know systemd will be screaming
at me again, but I interpreted this as needing a directory in
`/var/log/` chowned to snacuser. I still got the same error though
with logs in this directory, so I put it in `$HOME` instead. Same
error. Huh. Some frantic websearching led me (after multiple false
turns and worries about my filesystem) to a stackoverflow post[18].
I checked all the mentioned directives and found nothing, but the
comment "check man systemd.exec for what your version supports"[19]
later in the page, twigged that I should look up these directives.
As usual, I should RTFM...
Near the mentioned directives, I spotted something about the `User`
and `Group` directives being affected by the `DynamicUser`
directive, so I skimread that and it twigged that, despite setting
up the snacuser user, the systemd configuration was creating a
dynamic user on the fly with the same name. I commented out the
`DynamicUser` directive and, after some odd messages about changing
from a Dynamic user to a static one, I finally figured out what was
going on:
As the Systemd users knew from the start, the `DynamicUser`
directive creates a temporary user with access only to the
directory mentioned in the `StateDirectory` directive and passed to
the Exec with the `$STATE_DIRECTORY` variable. Everything else is
read-only at that point. Including the snac2 data directory! That's
why I couldn't access it from the web, as well as why the logs
didn't work! I rewrote the systemd configuration to something along
the lines of the following and it worked!
```
[Unit]
Description=A simple, minimalistic ActivityPub instance
Documentation=
https://codeberg.org/grunfink/snac2/src/branch/master/doc
After=network.target
Wants=network-online.target
[Service]
User=snacuser
Group=snacuser
ExecStart=/usr/local/bin/snac httpd /home/snacuser/snacdata
Restart=on-failure
[Install]
WantedBy=multi-user.target
# This is a systemd global service example. Edit and run:
#
# cp snac-global.service /etc/systemd/system/snac.service
# sudo snac init /var/lib/snac
# sudo snac adduser /var/lib/snac USER
# systemctl enable snac
# systemctl start snac
```
=>
https://forum.tailscale.com/t/funnel-with-nginx/3878 [16]:
Tailscale funnel with NginX forum query
=>
https://tailscale.com/kb/1311/tailscale-funnel#funnel-command-flags
[17]: Tailscale funnel commandline documentation
=>
https://serverfault.com/questions/847156/is-there-a-way-to-control-filesystem-access-with-systemd
[18]: StackOverflow post entitled "Is there a way to control
filesystem access with systemd?"
=>
https://serverfault.com/questions/847156/is-there-a-way-to-control-filesystem-access-with-systemd#comment1086672_847172
[19]: The comment
## Finishing up...
I set up a custom stylesheet, based on the "lowkey cyber dark"
theme[20] one and made funnel host that too. My tunnel config is
now basically:
```
sudo tailscale funnel --bg=true --set-path /.well-known/webfinger
localhost:8001/.well-known/webfinger
sudo tailscale funnel --bg=true --set-path /.well-known/host-meta
localhost:8001/.well-known/host-meta
sudo tailscale funnel --bg=true --set-path /.well-known/nodeinfo
localhost:8001/.well-known/nodeinfo
sudo tailscale funnel --bg=true --set-path /api/v1/
localhost:8001/api/v1/
sudo tailscale funnel --bg=true --set-path /api/v2/
localhost:8001/api/v2/
sudo tailscale funnel --bg=true --set-path /oauth/
localhost:8001/oauth/
sudo tailscale funnel --bg=true --set-path / localhost:8001/
sudo tailscale funnel --bg=true --set-path /lowtest.css
/home/snacuser/css/lowkey-test.css
```
I haven't tested this with Apps, but it hopefully will work.
One thing I have found is that I'm getting errors regarding
webfinger:
```
output message: sent to inbox
https://retro.social/inbox 401
[{"error":"Webfinger error when resolving dheadshot@sna...]
```
with lots of requeue errors later, so I haven't completely ironed
out the gremlins. I suspect that this is something to do with being
behind Tailscale funnel though, so might not be fixable.
The server isn't currently permanently running, so not always
accessible. This could cause errors with ActivityPub and
federating, but things worked fine yesterday and seem to today (and
seem faster today, even!).
I'll keep the server's address not too public at the moment as I
dodn't know how it'll handle load, but it's up as I type this!
=>
https://codeberg.org/swansinflight/snac-css/src/branch/main/lowkey-cyber-dark.css
[20]: "lowkey cyber dark" theme CSS
## Conclusion
Not much left for this post except to thank grunfink for building
snac2 in the first place, so:
```
********** ** **
/////**/// /** /** ** **
/** /** ****** ******* /** ** //** ** ****** ** **
/** /****** //////** //**///**/** ** //*** **////**/** /**
/** /**///** ******* /** /**/**** /** /** /**/** /**
/** /** /** **////** /** /**/**/** ** /** /**/** /**
/** /** /**//******** *** /**/**//** ** //****** //******
// // // //////// /// // // // // ////// //////
**** ** **
***** /**/ // /**
**///** ****** ** ** ******* ****** ** ******* /** **
/** /**//**//*/** /**//**///**///**/ /**//**///**/** **
//****** /** / /** /** /** /** /** /** /** /**/****
/////** /** /** /** /** /** /** /** /** /**/**/**
***** /*** //****** *** /** /** /** *** /**/**//**
///// /// ////// /// // // // /// // // //
```
## Update
Edited on the 6th of December 2024 (once I got OAuth etc. working)
to fix the tailscale funnel commands. Turns out things like:
```
sudo tailscale funnel --bg=true --set-path /api/v1/ localhost:8001/
```
should really be:
```
sudo tailscale funnel --bg=true --set-path /api/v1/
localhost:8001/api/v1/
```