complete-computing-environment/akkoma.org

547 lines
18 KiB
Org Mode

:PROPERTIES:
:ID: 20221202T122017.620403
:ROAM_REFS: https://akkoma.dev/AkkomaGang/akkoma https://akkoma.social/
:END:
#+TITLE: Self-Hosting on the Fediverse with Akkoma
#+FILETAGS: :Akkoma Social:
#+ARCOLOGY_KEY: cce/akkoma
Akkoma is a [[id:62538db5-d94a-47c3-9998-086ded91fd88][Fediverse]]/[[id:activitypub][ActivityPub]] server forked from [[roam:Pleroma]] written in [[id:cce/elixir][Elixir]], supporting the [[id:339daa8c-cc01-4654-aa89-330a4e62aafa][Mastodon Server]] API. This is a light-weight thing and I intend to self-publish to the Fediverse with an instance running on [[id:20211120T220054.226284][The Wobserver]].
* [[https://github.com/NixOS/nixpkgs/pull/192285][akkoma: init at 3.4.0 by illdefined · Pull Request #192285 · NixOS/nixpkgs]]
:PROPERTIES:
:ROAM_REF: [[https://github.com/NixOS/nixpkgs/pull/192285]]
:ID: 20221202T122230.525913
:END:
[2022-12-02 Fri 12:22]
* Akkoma on [[id:c75d20e6-8888-4c5a-ac97-5997e2f1c711][NixOS]]
:PROPERTIES:
:ID: 20221202T122135.502628
:ROAM_ALIASES: "Akkoma Server"
:END:
:LOGBOOK:
CLOCK: [2022-12-02 Fri 12:22]--[2022-12-02 Fri 16:24] => 4:02
:END:
The configuration interface in NixOS is nicer but also quite complicated. I have this broken up a bit and it consists of *most* of the configuration but not things like emoji and some other static assets like my favicon and whatnot.
It's not super complicated but we'll break it up in to multiple imports so that I can explain what is going on a bit:
#+ARROYO_SYSTEM_ROLE: server
#+ARROYO_NIXOS_MODULE: nixos/akkoma.nix
=myAkkoma= carries an [[https://akkoma.dev/AkkomaGang/akkoma/commit/af7c3fab98f4f5d1fa541035fd8b2821e0abb77b][unreleased patch]] to skip over the Pleroma observability rules -- i need to fix the [[id:20220101T190353.843667][Wobservability]] section below to use this anyways, but I couldn't turn off the observability rules because they were in the DB configuration. Oughtta make sure I move that stuff out in to =config.exs= fairly often.
#+begin_src nix :tangle ~/arroyo-nix/nixos/akkoma.nix :noweb yes
{ config, pkgs, lib, ... }:
let
myAkkoma = pkgs.akkoma.overrideAttrs (old: old // {
# patches = [
# (<arroyo/files/akkoma-fix-pleroma-migration.patch>)
# ];
});
in{
imports = [
./akkoma-users.nix
./akkoma-statics.nix
./akkoma-frontends.nix
./akkoma-virtualhost.nix
./akkoma-wobservability.nix
./akkoma-pwa.nix
./akkoma-config.nix
./akkoma-mrf.nix
./akkoma-search.nix
];
services.postgresql.ensureDatabases = ["akkoma"];
# have to run psql for migrations to pass:
# ALTER DATABASE akkoma OWNER TO akkoma;
services.postgresql.ensureUsers = [
{
name = "akkoma";
ensurePermissions = {
"DATABASE akkoma" = "ALL PRIVILEGES";
};
}
];
services.akkoma = {
enable = true;
package = myAkkoma;
group = "akkoma";
user = "akkoma";
};
}
#+end_src
** Akkoma Service Configuration
ref [[https://docs.akkoma.dev/stable/configuration/cheatsheet/][Configuration Cheat Sheet]], none of this is particularly exciting.
#+begin_src nix :tangle ~/arroyo-nix/nixos/akkoma-config.nix
{ config, pkgs, ... }:
{
services.akkoma.extraPackages = with pkgs; [exiftool ffmpeg-headless imagemagick];
# don't feel like setting password._secret here...
services.akkoma.initDb.enable = false;
services.akkoma.config = {
":pleroma".":instance" = {
name = "Computers :(";
description = "rrix's fediverse home";
email = "fedi@whatthefuck.computer";
registrations_open = false;
limit = 5000;
local_bubble = [
"botsin.space"
"hackers.town"
"tenforward.social"
"regenerate.social"
];
export_prometheus_metrics = true;
static_dir = "/srv/akkoma/static";
upload_dir = "/srv/akkoma/uploads";
};
":pleroma".":static_fe".enabled = true;
":pleroma"."Pleroma.Repo" = {
adapter = (pkgs.formats.elixirConf { }).lib.mkRaw "Ecto.Adapters.Postgres";
username = config.services.akkoma.user;
database = "akkoma";
hostname = "localhost";
timeout = 30000;
prepare = (pkgs.formats.elixirConf { }).lib.mkRaw ":named";
parameters = {
plan_cache_mode = "force_custom_plan";
};
};
":pleroma".":configurable_from_database" = true;
":pleroma".":media_proxy" = {
enabled = true;
proxy_opts.redirect_on_failure = true;
};
":pleroma"."Pleroma.Upload".filters =
map (pkgs.formats.elixirConf { }).lib.mkRaw [
"Pleroma.Upload.Filter.Exiftool"
"Pleroma.Upload.Filter.Dedupe"
"Pleroma.Upload.Filter.AnonymizeFilename"
];
":pleroma"."Pleroma.Web.Endpoint" = {
http = {
ip = "127.0.0.1";
port = 4000;
};
url = {
host = "notes.whatthefuck.computer";
scheme = "https";
port = 443;
};
};
# secrets
":pleroma"."Pleroma.Repo".password._secret = "/srv/akkoma/dbpass";
":joken".":default_signer"._secret = "/srv/akkoma/jwt-signer";
":pleroma"."Pleroma.Web.Endpoint" = {
live_view.signing_salt._secret = "/srv/akkoma/liveview-salt";
secret_key_base._secret = "/srv/akkoma/secret-key-base";
signing_salt._secret = "/srv/akkoma/signing-salt";
};
":web_push_encryption".":vapid_details" = {
private_key._secret = "/srv/akkoma/vapid-private";
public_key._secret = "/srv/akkoma/vapid-public";
subject = "mailto:fedi@whatthefuck.computer";
};
};
}
#+end_src
** System Users
I really would like to manage my uids and gids better, but alas.
#+begin_src nix :tangle ~/arroyo-nix/nixos/akkoma-users.nix
{ config, pkgs, ... }:
{
ids.uids.akkoma = 901;
ids.gids.akkoma = 901;
users.groups.akkoma = {
gid = config.ids.gids.akkoma;
};
users.users.akkoma = {
group = "akkoma";
uid = config.ids.uids.akkoma;
shell = "${pkgs.bash}/bin/bash";
isSystemUser = true;
# ugh... services.pleroma.stateDir is readonly
home = "/var/lib/pleroma";
createHome = true;
};
}
#+end_src
** Frontend Management
so if you set a =static_dir= to a mutable directory on the server, the NixOS module won't install the frontends you ask for. This is good and fine, and not a surprise, certainly not undocumented. So what if you just:
=pleroma_ctl frontend install admin-fe --ref stable=
=pleroma_ctl frontend install fedibird-fe --ref akkoma=
=pleroma_ctl frontend install pleroma-fe --ref stable=
That fails because =BindReadOnlyPaths= is set in the NixOS module as part of the hardening of the service. I shouldn't mind, but =** (File.Error) could not make directory (with -p) "/srv/akkoma/static/frontends/tmp": read-only file system=
so, okay, make sure =BindReadOnlyPaths= is not set, and set =ReadWritePaths= just to be sure.
In theory it'd be possible to use a =system.activationScripts= entry to take =frontends.$foo.package= and slam a symlink in like I do for the terms of service above, but i'm tired.
#+begin_src nix :tangle ~/arroyo-nix/nixos/akkoma-frontends.nix
{ config, pkgs, lib, ... }:
{
systemd.services.akkoma.serviceConfig.ReadWritePaths = [ config.services.akkoma.config.":pleroma".":instance".static_dir ];
systemd.services.akkoma.serviceConfig.BindReadOnlyPaths = lib.mkForce [ ];
services.akkoma.frontends = {
primary = {
name = "pleroma-fe";
ref = "stable";
};
mastodon= {
name = "fedibird-fe";
ref = "akkoma";
};
admin = {
name = "admin-fe";
ref = "stable";
};
};
# shove this thing in a system.activationScript to symlink to static_dir/frontends
# frontends.primary.package = pkgs.runCommand "akkoma-fe" {
# config = builtins.toJSON {
# expertLevel = 1;
# collapseMessageWithSubject = false;
# replyVisibility = "following";
# hideScopeNotice = true;
# renderMisskeyMarkdown = false;
# hideSiteFavicon = true;
# postContentType = "text/markdown";
# showNavShortcuts = false;
# };
# nativeBuildInputs = with pkgs; [ jq xorg.lndir ];
# passAsFile = [ "config" ];
# } ''
# mkdir $out
# lndir ${pkgs.akkoma-frontends.akkoma-fe} $out
#
# rm $out/static/config.json
# jq -s add ${pkgs.akkoma-frontends.akkoma-fe}/static/config.json ${config} \
# >$out/static/config.json
# '';
}
#+end_src
** Nginx Frontend for Akkoma
Nothing special here -- I have it split in to two blocks here because one of my old iterations of [[id:1d917282-ecf4-4d4c-ba49-628cbb4bb8cc][The Arcology Project]] used this domain to host short-form [[id:fa6cee69-dca0-45f5-ae9e-b71cad3702a6][IndieWeb]]/microformat notes. The old files still exist and can be resolved in the =try_files= block, and any failures will proxy through to the app backend. I also adjust the max body size for image uploads, etc. I might replace that with S3 in the future but for now the images can just go on to the file system.
#+begin_src nix :tangle ~/arroyo-nix/nixos/akkoma-virtualhost.nix
{ ... }:
{
services.nginx.virtualHosts."notes.whatthefuck.computer" = {
root = "/srv/static-sites/notes.whatthefuck.computer/_site";
extraConfig = ''
client_max_body_size 100M;
'';
locations."/".extraConfig = ''
try_files $uri @proxy;
'';
locations."@proxy" = {
proxyPass = "http://127.0.0.1:4000";
extraConfig = ''
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $http_host;
'';
};
locations."/phoenix/live_dashboard".extraConfig = ''
auth_basic "closed site";
auth_basic_user_file /etc/nginx-htpasswd;
'';
};
}
#+end_src
** Static Files
I could just splat this on to the filesystem but no harm in having it in the Nix store:
#+begin_src html :noweb-ref tos
<p>
Now look; this is a single-user
instance. <a href="https://notes.whatthefuck.computer/rrix">rrix</a>
posts inane bullshit here. Look at their profile if you care about
what is going on here.
</p>
<p>
<ul>
<li>I'm not a fascist.</li>
<li>I'm not a cop.</li>
<li>I'm not a narc.</li>
<li>I'm not a racist.</li>
<li>I'm not a transphobe.</li>
<li>I'm not gonna put up with bullshit.</li>
<li>I'm just a little computer goblin who wants to self-host.</li>
</ul>
</p>
<p>
If you care about the privacy policy of this instance, don't
federate with it. rrix is a consummate privacy professional, but
they're also just one person. I have no intention to do anything
untoward with posts federated to my instance, nor engage in
non-standard behavior on the fediverse, the NixOS code which deploys
all the software on this server
is <a href="https://cce.whatthefuck.computer/akkoma">available
online</a>.
</p>
<p>
At the same time, I'm likely not going to be able to go
up against government requests for data stored on this instace. As
of [2023-02-16] this instance has not been compelled to give data to
any government or law enforcement agency and has not done so
voluntarily. I'm just one homie hanging out making posts with my
friends and trying to make new ones, and you're here reading
this. What's up?
</p>
#+end_src
#+begin_src nix :tangle ~/arroyo-nix/nixos/akkoma-statics.nix :noweb yes
{ config, pkgs, ... }:
let
tos = pkgs.writeTextFile {
name="pleroma-terms-of-service";
text = ''
<<tos>
'';
};
in
{
# services.akkoma.extraStatic."static/terms-of-service.html" = tos;
system.activationScripts.install-pleroma-tos.text = ''
echo "Installing Pleroma Terms of Service to static directory"
export DEST_DIR=/srv/akkoma/static/
mkdir -p $DEST_DIR
ln -sf ${tos} $DEST_DIR/static/terms-of-service.html
'';
}
#+end_src
** [[id:20220101T190353.843667][Wobservability]]
I would like to have some usage metrics emitted, this is just service-level stuff:
Enable =Pleroma.Web.Endpoint.MetricsExporter= in settings.
#+begin_src nix :tangle ~/arroyo-nix/nixos/akkoma-wobservability.nix
{ ... }:
{
services.prometheus.scrapeConfigs = [
{
job_name = "akkoma";
metrics_path = "/api/pleroma/app_metrics";
static_configs = [{ targets = [ "127.0.0.1:4000" ]; }];
}
];
}
#+end_src
** PWA manifest for Pleroma-FE
Pleroma's mute filters, etc aren't 1-1 compatible with Mastodon API so they don't work in Mastodon-FE or Subway Tooter... This is ...unfortunate. Let's see how using Pleroma-FE as a [[https://developer.mozilla.org/en-US/docs/Web/Manifest][Progressive Web App]] goes..
#+begin_src json :tangle ~/arroyo-nix/files/notes-pwa.json
{
"$schema": "https://json.schemastore.org/web-manifest-combined.json",
"name": "Fedi Notes",
"short_name": "Fedi Notes",
"start_url": "https://notes.whatthefuck.computer/",
"display": "standalone",
"background_color": "#004daa",
"theme_color": "#31363b",
"description": "Pleroma-FE on notes.whatthefuck.computer",
"icons": [{
"src": "https://notes.whatthefuck.computer/media/e8ecd309a5e3d99736beff056fb461ba157dc4ff9317f3cc6063c4ce280bd604.blob",
"sizes": "312x312",
"type": "image/png"
}]
}
#+end_src
This needs to be shoved in to =<head>= and there's no way to do that directly in Pleroma-FE so I make a silly little HTML page and shove it on to a virtualhost:
#+begin_src web :tangle ~/arroyo-nix/files/notes-pwa.html
<html>
<head>
<link rel="manifest" href="notes-pwa.json" />
</head>
<body>
<h1>Pleroma-FE as PWA</h1>
<p>
On browsers which support "add to home screen" or similar
things, you can create a Progressive Web App which will
load the <code>Computer :(</code> site in a dedicated frame.
</p>
</body>
</html>
#+end_src
And this is installed, like so:
#+begin_src nix :tangle ~/arroyo-nix/nixos/akkoma-pwa.nix
{ config, pkgs, ... }:
{
system.activationScripts = let
json = <arroyo/files/notes-pwa.json>;
html = <arroyo/files/notes-pwa.html>;
in {
install-pleroma-tos.text = ''
echo "Installing Pleroma PWA manifest and static page"
export DEST_DIR=/srv/static-sites/default
mkdir -p $DEST_DIR
ln -sf ${html} $DEST_DIR/pwa.html
ln -sf ${json} $DEST_DIR/notes-pwa.json
'';
};
services.nginx.virtualHosts."fontkeming.fail".extraConfig = ''
disable_symlinks off;
'';
}
#+end_src
## why 404?
** Enable =meilisearch= for Full Text Search of Toots
The Postgres FTS in Pleroma seemed like it worked better than the one in Akkoma? Very strange, to me. =meilisearch= is an elasticsearch-alike that is hopefully less shitty than elasticsearch.
This is configured based on [[https://docs.akkoma.dev/stable/configuration/search/#meilisearch][the docs]], and Akkoma's secret handling...
#+begin_src nix :tangle ~/arroyo-nix/nixos/akkoma-search.nix
{ config, pkgs, lib, ... }:
{
services.meilisearch = {
enable = true;
noAnalytics = true;
masterKeyEnvironmentFile = "/srv/meilisearch/key.txt";
environment = "production";
};
services.akkoma.config = {
":pleroma"."Pleroma.Search" = {
module = (pkgs.formats.elixirConf { }).lib.mkRaw "Pleroma.Search.Meilisearch";
};
":pleroma"."Pleroma.Search.Meilisearch" = {
url = "http://127.0.0.1:7700";
private_key._secret = "/srv/akkoma/meilisearch_key";
};
};
}
#+end_src
Okay so I updated my NixOS from 23.05 to 23.11 today and Meilisearch failed to start because the DB is incompatible between versions. You need a running instance to =curl= the dump endpoint, update, and then reimport. I decided to blow away the DB instead and reindex:
- =rm /var/lib/meilisearch/*=
- =systemctl start meilisearch=, look in the journal to see what the new master key is
- =pleroma_ctl search.meilisearch show-keys $THE_ADMIN_KEY= and take the "use for all operations" key and stick it in =/srv/akkoma/meilisearch_key= or whatever
- =systemctl restart akkoma-config && systemctl restart akkoma= to bring the secret in to the configuration
- =pleroma_ctl search.meilisearch index=
** NEXT Akkoma Moderation Rules
the new [[https://docs.akkoma.dev/stable-docs/configuration/mrf/][Message Rewrite Facility]] x [[https://fediblock.neocities.org/][fediblock]] dropped. I don't see a lot of this stuff and part of me thinks that having it boosted in to my TWKN or even home timeline is a great signal that I should be unfollowing whoever is bringing the filth in to my instance, but also I want to respect my own sanity.
#+begin_src nix :tangle ~/arroyo-nix/nixos/akkoma-mrf.nix :noweb yes
{ pkgs, ... }:
let ecf = pkgs.formats.elixirConf {};
in
{
services.akkoma.config = {
":pleroma".":instance" = {
quarantined_instances = [
<<quarantined_instances()>>
];
};
":pleroma".":mrf".policies = ecf.lib.mkRaw "Pleroma.Web.ActivityPub.MRF.SimplePolicy";
":pleroma".":mrf_simple".reject = [
<<reject_instances()>>
];
};
}
#+end_src
These two functions generate those from a table I don't export.
#+NAME: quarantined_instances
#+begin_src emacs-lisp :noweb-ref quarantined_instances :var tbl=blocked-domains
(->> tbl
(-map #'first)
(--map (format "\"%s\"" it))
(s-join "\n"))
#+end_src
This elixirConf stuff, and the declarative config model, is pretty slick. [[file:~/Code/nixpkgs/pkgs/pkgs-lib/formats.nix::{ lib, pkgs }:][<nixpkgs/pkgs/pkgs-lib/formats.nix>]]
#+NAME: reject_instances
#+begin_src emacs-lisp :noweb-ref reject_instances :var tbl=blocked-domains
(->> tbl
(--map (format "(ecf.lib.mkTuple [\"%s\" \"%s\"])" (first it) (second it)))
(s-join "\n"))
#+end_src
*** Rules aren't exported for personal safety :noexport:
#+NAME: blocked-domains
| domain | reason |
|-------------------------+-------------------|
| poa.st | ugh |
| bae.st | ughh |
| coon.town | ughhh |
| kiwifarms.cc | ughhh |
| chungus.cc | unmoderated |
| cawfee.club | unmoderated |
| freespeechextremist.com | ugh |
| gab.com | ughhh |
| truthsocial.com | lol |
| 80percent.social | ugh + spam |
| rakket.app | ughh |
| 13bells.com | religious weirdos |
| pieville.net | nazis |
| yggdrasil.social | |
| glindr.org | transphobia |
| leafposter.club | racism |
| masochi.st | awful TWKN |
| pedo.school | yanno |