To install tootik, you need to:
- Get a server and point a domain to it
- Generate a valid HTTPS certificate (we're using a free Let's Encrypt certificate) and a Gemini certificate
- Manually run tootik on your server (we're using a prebuilt static executable for x86 or ARM)
- Verify that the Gemini frontend is accessible and works
- Verify that federation works in both directions
- Stop tootik and configure your machine to keep it running at all times
- Set up a server with a public address; preferably, both IPv4 and IPv6.
The $5/mo nodes at Linode (disclaimer: referral link) should be more than enough to run tootik, but this guide should work on any Debian 11 or 12 machine. (If you wish to help fund the development of tootik, use the referral link. Thank you!)
- Register a domain and make sure your domain points to the server. Wait until
getent hosts $domain
ornslookup $domain
returns the server's public address.
gen.xyz offers cheap domains but you can use any domain as long as it's not blocked by other ActivityPub-compatible servers or has bad reputation.
In every command that appears in this guide, replace $domain
with your domain or simply run domain=
followed by your domain now.
- Install Certbot and generate a HTTPS certificate for federation:
apt update
apt install snapd
snap install --classic certbot
ln -s /snap/bin/certbot /usr/bin/certbot
certbot certonly --standalone
- Create a directory for tootik files and copy the certificate:
mkdir /tootik-cfg
cp /etc/letsencrypt/live/*/fullchain.pem /tootik-cfg/https-cert.pem
cp /etc/letsencrypt/live/*/privkey.pem /tootik-cfg/https-key.pem
- Create a post-renewal hook that updates the copied certificate on renewal:
cat << EOF > /etc/letsencrypt/renewal-hooks/deploy/tootik.sh
#!/bin/sh
cp -f /etc/letsencrypt/live/*/fullchain.pem /tootik-cfg/https-cert.pem
cp -f /etc/letsencrypt/live/*/privkey.pem /tootik-cfg/https-key.pem
EOF
chmod 755 /etc/letsencrypt/renewal-hooks/deploy/tootik.sh
tootik monitors these files for changes and should restart its HTTPS listener automatically every time the certificate is renewed and the hook replaces the files.
- Generate a self-signed TLS certificate for the Gemini frontend
openssl ecparam -name prime256v1 -genkey -out /tmp/ec.pem
openssl req -new -x509 -key /tmp/ec.pem -sha256 -nodes -subj "/CN=$domain" -out /tootik-cfg/gemini-cert.pem -keyout /tootik-cfg/gemini-key.pem -days 3650
- Download the Garden Fence blocklist:
curl -L https://github.com/gardenfence/blocklist/raw/main/gardenfence-mastodon.csv > /tootik-cfg/gardenfence-mastodon.csv
- Create an unprivileged user and a separate directory for the tootik database, then download tootik and run it:
mkdir /tootik-data
useradd -mr tootik
chown -R tootik:tootik /tootik-cfg /tootik-data
curl -L https://github.com/dimkr/tootik/releases/latest/download/tootik-$(case `uname -m` in x86_64) echo amd64;; aarch64) echo arm64;; i686) echo 386;; armv7l) echo arm;; esac) -o /usr/local/bin/tootik
chmod 755 /usr/local/bin/tootik
tootik -domain $domain -addr :443 -gemaddr :1965 -gopheraddr :70 -fingeraddr :79 -blocklist /tootik-cfg/gardenfence-mastodon.csv -cert /tootik-cfg/https-cert.pem -key /tootik-cfg/https-key.pem -gemcert /tootik-cfg/gemini-cert.pem -gemkey /tootik-cfg/gemini-key.pem -db /tootik-data/db.sqlite3
To enable more verbose logging, add -loglevel -4
.
We use a separate directory for the database because tootik monitors the directory that contains the HTTPS certificate and the directory that contains the blocklist for changes, so it can reload these files when they get replaced or modified. The database changes often, so putting the database in the same directory as the files tootik monitors for changes can result in many wakeups and increased CPU usage.
tootik writes logs to stderr. Keep this shell open for troubleshooting purposes, and continue in another.
- From a remote machine, verify that tootik is accessible over HTTPS:
curl -v https://$domain
Output should contain:
< HTTP/2 301
< location: gemini://$domain
If curl
times out, check your server's firewall: port 443 is probably blocked.
- Verify that tootik is accessible from a remote machine over Gemini, with any Gemini client.
If you have a graphical web browser and a Gemini client that configures itself as the default handler for gemini:// URLs, opening https://$domain through the web browser should display a popup that asks you to use the Gemini client instead. Otherwise, fire up your Gemini client and navigate to gemini://$domain.
- Register by creating a client certificate or clicking "Sign in" and use "View profile" to verify that your instance is able to "discover" users on other servers.
Once a user is discovered, you can follow this user and your instance should start receiving new posts by this user. They should appear under your user's inbox ("My feed") after a while (FeedUpdateInterval
) and the user's profile.
If you don't see any posts, check tootik's output.
If certificate validation fails for all outgoing requests, try to update CA certificates using apt update && apt-get install --only-upgrade ca-certificates
and synchronize the server's clock using apt install systemd-timesyncd && timedatectl set-ntp true
.
- Repeat the same check in the other direction: try to search for your user in your tootik instance (
$user@$domain
) from another ActivityPub-compatible server, then follow it and check if the other server receives new posts by your tootik user.
If you don't see any posts, check tootik's output.
-
Ask tootik to stop using CTRL+c and wait.
-
Add a systemd unit for tootik, to make it run at startup, restart it if it crashes, and save its log on disk (with log rotation):
cat << EOF > /etc/systemd/system/tootik.service
[Unit]
Description=tootik
After=network.target
[Service]
ExecStart=tootik -domain $domain -addr :443 -gemaddr :1965 -gopheraddr :70 -fingeraddr :79 -blocklist /tootik-cfg/gardenfence-mastodon.csv -cert /tootik-cfg/https-cert.pem -key /tootik-cfg/https-key.pem -gemcert /tootik-cfg/gemini-cert.pem -gemkey /tootik-cfg/gemini-key.pem -db /tootik-data/db.sqlite3
User=tootik
Group=tootik
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
Restart=always
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable tootik
systemctl start tootik
Now you can view tootik logs using journalctl
, e.g.:
journalctl -u tootik -S '5 minutes ago' -f
To stop tootik:
systemctl stop tootik
To start tootik:
systemctl start tootik
To check the tootik version:
tootik -version
To disable new user registration, add -closed
to the tootik command-line in ExecStart
and restart it:
sed -i 's/^ExecStart=.*/& -closed/' /etc/systemd/system/tootik.service
systemctl daemon-reload
systemctl restart tootik
To disable HTTPS and make tootik speak HTTP instead (useful if you wish to run tootik behind a HTTPS reverse proxy), add -plain
to the tootik command-line in ExecStart
and restart it:
sed -i 's/^ExecStart=.*/& -plain/' /etc/systemd/system/tootik.service
systemctl daemon-reload
systemctl restart tootik
To view and change tootik configuration defaults:
tootik -dumpcfg > /tootik-cfg/cfg.json
# edit /tootik-cfg/cfg.json
sed -i 's~^ExecStart=.*~& -cfg /tootik-cfg/cfg.json~' /etc/systemd/system/tootik.service
systemctl daemon-reload
systemctl restart tootik
To update and restart tootik:
systemctl stop tootik
curl -L https://github.com/dimkr/tootik/releases/latest/download/tootik-$(case `uname -m` in x86_64) echo amd64;; aarch64) echo arm64;; i686) echo 386;; armv7l) echo arm;; esac) -o /usr/local/bin/tootik
systemctl start tootik
To add a community, then set its bio and avatar:
tootik -domain $domain -db /tootik-data/db.sqlite3 add-community fountainpens
# put bio in /tmp/bio.txt
tootik -domain $domain -db /tootik-data/db.sqlite3 set-bio fountainpens /tmp/bio.txt
# put avatar in /tmp/avatar.png
tootik -domain $domain -db /tootik-data/db.sqlite3 set-avatar fountainpens /tmp/avatar.png
- Run tootik with
-plain
, so it speaks HTTP and the reverse proxy handles TLS.- Ensure the reverse proxy uses a valid TLS certificate
- Use
-addr
(i.e.-addr 127.0.0.1:8080
) to specify the port used by tootik's HTTP listener. - Use
-domain
to specify the external host and port combination other servers use to talk to your instance:- If tootik runs on
example.com
with-addr 127.0.0.1:8080 -plain
with a reverse proxy on port 443, pass-domain example.com
- If tootik runs on
example.com
with-addr 127.0.0.1:8080 -plain
with a reverse proxy on port 8443, pass-domain example.com:8443
- If tootik runs on
- Forward requests from the reverse proxy to tootik.
- Preserve the
Signature
header when forwarding POST requests to/inbox/$user
, otherwise tootik cannot validate incoming requests - Preserve the
Collection-Synchronization
header when forwarding POST requests to/inbox/$user
if you want follower synchronization to work (recommended)
- Preserve the
- If tootik's HTTPS listener uses a port other than 443 (say, tootik runs with
-addr :8888
) and this is the port other instances use to talk to tootik,-domain
must include the port (for example,-domain example.com:8888
). - If tootik is behind a proxy, make sure the proxy passes the
Signature
header to tootik. - grep logs for
actor is too young
and decreaseMinActorAge
if the federated account you're trying to talk to is newly registered.
To protect the server and the user data on it, it's recommended to restrict SSH access.
First, change the SSH port from the default of 22 to something else:
sed -i 's/^#Port 22/Port 7676/' /etc/ssh/sshd_config
systemctl restart ssh.service
Then, hide this listening port from scanners using port knocking:
for x in iptables ip6tables; do
$x -N ssh
$x -A INPUT -p tcp --dport 7676 -j ssh
$x -A ssh -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
$x -A ssh -p tcp --sport 1234 -m recent --name knock1 --set -j DROP
$x -A ssh -m conntrack --ctstate NEW -m recent --name knock1 \! --rcheck --seconds 50 -j DROP
$x -A ssh -p tcp --sport 2345 -m recent --name knock2 --set -j DROP
$x -A ssh -m conntrack --ctstate NEW -m recent --name knock2 \! --rcheck --seconds 40 -j DROP
$x -A ssh -p tcp --sport 3456 -m recent --name knock3 --set -j DROP
$x -A ssh -m conntrack --ctstate NEW -m recent --name knock3 \! --rcheck --seconds 30 -j DROP
$x -A ssh -j ACCEPT
done
To connect:
for x in 1234 2345 3456; do nc -vw1 -p $x example.com 7676; done
ssh -p 7676 [email protected]
To reapply these SSH port restrictions after reboot:
apt install iptables-persistent
iptables-save > /etc/iptables/rules.v4
ip6tables-save > /etc/iptables/rules.v6
tootik handles /robots.txt requests over both HTTP and Gemini: it asks all kinds of crawlers to leave it alone. However, some crawlers don't obey what robots.txt says and flood the server with requests. They visit all posts they can find, discover users (post authors, reply authors and mentioned users), then view the profiles of these users to discover more posts and more users.
Therefore, it's recommended to have some kind of rate limiting for Gemini requests:
for x in iptables ip6tables; do
$x -N ratelimit
$x -A ratelimit -j LOG --log-prefix "rate limit "
$x -A ratelimit -j DROP
$x -I INPUT -p tcp --dport 1965 -m conntrack --ctstate NEW -m hashlimit --hashlimit-name rate --hashlimit-mode srcip --hashlimit-above 100/hour -j ratelimit
done
To reapply rate limiting after reboot:
apt install iptables-persistent
iptables-save > /etc/iptables/rules.v4
ip6tables-save > /etc/iptables/rules.v6