{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.akkoma;
ex = cfg.config;
db = ex.":pleroma"."Pleroma.Repo";
web = ex.":pleroma"."Pleroma.Web.Endpoint";
isConfined = config.systemd.services.akkoma.confinement.enable;
hasSmtp = (attrByPath [ ":pleroma" "Pleroma.Emails.Mailer" "adapter" "value" ] null ex) == "Swoosh.Adapters.SMTP";
isAbsolutePath = v: isString v && substring 0 1 v == "/";
isSecret = v: isAttrs v && v ? _secret && isAbsolutePath v._secret;
absolutePath = with types; mkOptionType {
name = "absolutePath";
description = "absolute path";
descriptionClass = "noun";
check = isAbsolutePath;
inherit (str) merge;
};
secret = mkOptionType {
name = "secret";
description = "secret value";
descriptionClass = "noun";
check = isSecret;
nestedTypes = {
_secret = absolutePath;
};
};
ipAddress = with types; mkOptionType {
name = "ipAddress";
description = "IPv4 or IPv6 address";
descriptionClass = "conjunction";
check = x: str.check x && builtins.match "[.0-9:A-Fa-f]+" x != null;
inherit (str) merge;
};
elixirValue = let
elixirValue' = with types;
nullOr (oneOf [ bool int float str (attrsOf elixirValue') (listOf elixirValue') ]) // {
description = "Elixir value";
};
in elixirValue';
frontend = {
options = {
package = mkOption {
type = types.package;
description = mdDoc "Akkoma frontend package.";
example = literalExpression "pkgs.akkoma-frontends.akkoma-fe";
};
name = mkOption {
type = types.nonEmptyStr;
description = mdDoc "Akkoma frontend name.";
example = "akkoma-fe";
};
ref = mkOption {
type = types.nonEmptyStr;
description = mdDoc "Akkoma frontend reference.";
example = "stable";
};
};
};
sha256 = builtins.hashString "sha256";
replaceSec = let
replaceSec' = { }@args: v:
if isAttrs v
then if v ? _secret
then if isAbsolutePath v._secret
then sha256 v._secret
else abort "Invalid secret path (_secret = ${v._secret})"
else mapAttrs (_: val: replaceSec' args val) v
else if isList v
then map (replaceSec' args) v
else v;
in replaceSec' { };
# Erlang/Elixir uses a somewhat special format for IP addresses
erlAddr = addr: fileContents
(pkgs.runCommand addr {
nativeBuildInputs = [ cfg.package.elixirPackage ];
code = ''
case :inet.parse_address('${addr}') do
{:ok, addr} -> IO.inspect addr
{:error, _} -> System.halt(65)
end
'';
passAsFile = [ "code" ];
} ''elixir "$codePath" >"$out"'');
format = pkgs.formats.elixirConf { elixir = cfg.package.elixirPackage; };
configFile = format.generate "config.exs"
(replaceSec
(attrsets.updateManyAttrsByPath [{
path = [ ":pleroma" "Pleroma.Web.Endpoint" "http" "ip" ];
update = addr:
if isAbsolutePath addr
then format.lib.mkTuple
[ (format.lib.mkAtom ":local") addr ]
else format.lib.mkRaw (erlAddr addr);
}] cfg.config));
writeShell = { name, text, runtimeInputs ? [ ] }:
pkgs.writeShellApplication { inherit name text runtimeInputs; } + "/bin/${name}";
genScript = writeShell {
name = "akkoma-gen-cookie";
runtimeInputs = with pkgs; [ coreutils util-linux ];
text = ''
install -m 0400 \
-o ${escapeShellArg cfg.user } \
-g ${escapeShellArg cfg.group} \
<(hexdump -n 16 -e '"%02x"' /dev/urandom) \
"$RUNTIME_DIRECTORY/cookie"
'';
};
copyScript = writeShell {
name = "akkoma-copy-cookie";
runtimeInputs = with pkgs; [ coreutils ];
text = ''
install -m 0400 \
-o ${escapeShellArg cfg.user} \
-g ${escapeShellArg cfg.group} \
${escapeShellArg cfg.dist.cookie._secret} \
"$RUNTIME_DIRECTORY/cookie"
'';
};
secretPaths = catAttrs "_secret" (collect isSecret cfg.config);
vapidKeygen = pkgs.writeText "vapidKeygen.exs" ''
[public_path, private_path] = System.argv()
{public_key, private_key} = :crypto.generate_key :ecdh, :prime256v1
File.write! public_path, Base.url_encode64(public_key, padding: false)
File.write! private_path, Base.url_encode64(private_key, padding: false)
'';
initSecretsScript = writeShell {
name = "akkoma-init-secrets";
runtimeInputs