kittybox:
{ lib, ... }: let
hosts = ''
192.168.2.101 kittybox.test
192.168.2.102 another.test
'';
in {
name = "nixos-kittybox";
nodes = {
kittybox = { config, pkgs, lib, ... }: {
imports = [ kittybox.nixosModules.default ];
services.postgresql = {
enable = true;
ensureDatabases = ["kittybox"];
ensureUsers = [ {
name = "kittybox";
ensurePermissions = {
"DATABASE kittybox" = "ALL PRIVILEGES";
};
} ];
};
networking = {
interfaces.eth1 = {
ipv4.addresses = [
{ address = "192.168.2.101"; prefixLength = 24; }
];
};
extraHosts = hosts;
firewall.allowedTCPPorts = [ 443 ];
firewall.allowedUDPPorts = [ 443 ];
};
services.kittybox = {
enable = true;
logLevel = "info,kittybox=debug,retainer::cache=warn,h2=warn,rustls=warn";
backendUri = "postgres://localhost?host=/run/postgresql&dbname=kittybox";
jobQueueUri = config.services.kittybox.backendUri;
};
systemd.services.kittybox.wants = [ "postgresql.service" ];
systemd.services.kittybox.after = [ "postgresql.service" ];
systemd.services.kittybox.environment = {
"KITTYBOX_CUSTOM_PKI_ROOTS" = ./tls/rootCA.pem;
};
environment.systemPackages = with pkgs; [
xh
];
services.nginx = {
enable = true;
recommendedProxySettings = true;
virtualHosts = {
"kittybox.test" = {
forceSSL = true;
sslCertificate = ./tls/kittybox.test.pem;
sslCertificateKey = ./tls/kittybox.test-key.pem;
locations = {
"/" = {
proxyPass = "http://localhost:8080";
};
};
};
};
};
security.pki.certificates = [
(builtins.readFile ./tls/rootCA.pem)
];
};
another = { config, pkgs, lib, ... }: {
networking = {
interfaces.eth1 = {
ipv4.addresses = [
{ address = "192.168.2.102"; prefixLength = 24; }
];
};
extraHosts = hosts;
firewall.allowedTCPPorts = [ 443 ];
firewall.allowedUDPPorts = [ 443 ];
};
services.nginx = {
enable = true;
virtualHosts = {
"another.test" = {
forceSSL = true;
sslCertificate = ./tls/another.test.pem;
sslCertificateKey = ./tls/another.test-key.pem;
locations = {
"/" = {
root = ./webmention-test;
};
};
};
};
};
environment.systemPackages = with pkgs; [
xh
];
security.pki.certificates = [
(builtins.readFile ./tls/rootCA.pem)
];
};
};
testScript = ''
import json
import shlex
import time
post_mf2 = {
"type": ["h-entry"],
"properties": {
"uid": ["https://kittybox.test/posts/test-post"],
"url": ["https://kittybox.test/posts/test-post"],
"published": ["2023-07-22T14:04:53+0300"],
"content": [{"html": "<p>Hello! This is a test post.</p>"}],
"author": ["https://kittybox.test/"],
}
}
start_all()
with subtest("Verify that Kittybox started correctly..."):
kittybox.wait_for_unit("default.target")
kittybox.succeed("xh --no-check-status https://kittybox.test/.kittybox/micropub")
with subtest("Onboarding should correctly work..."):
kittybox.copy_from_host("${./onboarding.json}", "/root/onboarding.json")
kittybox.succeed("xh --follow https://kittybox.test/.kittybox/onboarding -j @/root/onboarding.json")
# Testing for a known string is the easiest way to determine that the onboarding worked
kittybox.succeed("xh https://kittybox.test/ | grep 'vestige of the past long gone'")
with subtest("The other host should also be reachable..."):
another.wait_for_unit("default.target")
another.succeed("xh https://another.test/ | grep 'This is a test webmention.'")
with subtest("Kittybox accepts a webmention to a valid post..."):
# Note: we don't really have a way to "authenticate" here.
# Let's insert the post manually.
print(kittybox.succeed("sudo -u postgres psql kittybox -c \"INSERT INTO kittybox.mf2_json (uid, mf2, owner) VALUES ('https://kittybox.test/posts/test-post', '" + json.dumps(post_mf2).replace("\"", "\\\"").replace("'", "'" * 2) + "', 'kittybox.test') RETURNING uid\""))
print(kittybox.succeed("sudo -u postgres psql -A kittybox -c \"SELECT mf2 FROM kittybox.mf2_json WHERE uid = 'https://kittybox.test/posts/test-post'\""))
kittybox.succeed("xh --verify ${./tls/rootCA.pem} https://kittybox.test/posts/test-post")
another.succeed("xh --verify ${./tls/rootCA.pem} https://kittybox.test/.kittybox/webmention --form source=https://another.test/index.html target=https://kittybox.test/posts/test-post")
# Wait a while to let the async tasks settle...
time.sleep(2)
# Ensure the webmention has propagated
# Kittybox doesn't fully render them yet, but the counters are there
kittybox.succeed("xh --verify ${./tls/rootCA.pem} https://kittybox.test/posts/test-post | grep " + shlex.quote('<span class="icon" aria-label="replies">💬</span><span class="counter">1</span>'))
'';
}