From 13c70168a4d62e1fb2f7e3ecd8dbe304059cf9e5 Mon Sep 17 00:00:00 2001 From: a-kenji Date: Tue, 30 Jul 2024 10:43:50 +0200 Subject: [PATCH] mumble: init clan-module This adds the `mumble` clan-module. This allows for voice chatting in a true peer-to-peer network. Every machine that has the module enabled is a potential host and client - every participant has the same role. It doesn't matter who in the network is online - as long as one of the machines is up, one server is up and people can start their voice chat. --- checks/flake-module.nix | 5 +- checks/mumble/default.nix | 150 +++++++++++ .../mumble/machines/peer1/facts/mumble-cert | 22 ++ checks/mumble/machines/peer1/key.age | 1 + checks/mumble/machines/peer1/peer_1_test_cert | 14 + checks/mumble/machines/peer1/peer_1_test_key | 6 + .../mumble/machines/peer2/facts/mumble-cert | 22 ++ checks/mumble/machines/peer2/peer_2_test_cert | 14 + checks/mumble/machines/peer2/peer_2_test_key | 6 + checks/mumble/peer_1/key.age | 1 + checks/mumble/peer_1/peer_1_test_cert | 22 ++ checks/mumble/peer_1/peer_1_test_key | 28 ++ checks/mumble/peer_2/peer_2_test_cert | 22 ++ checks/mumble/peer_2/peer_2_test_key | 28 ++ clanModules/flake-module.nix | 1 + clanModules/mumble/README.md | 14 + clanModules/mumble/default.nix | 105 ++++++++ .../mumble/mumble-populate-channels.py | 249 ++++++++++++++++++ clanModules/mumble/test.nix | 42 +++ docs/mkdocs.yml | 1 + 20 files changed, 751 insertions(+), 2 deletions(-) create mode 100644 checks/mumble/default.nix create mode 100644 checks/mumble/machines/peer1/facts/mumble-cert create mode 100644 checks/mumble/machines/peer1/key.age create mode 100644 checks/mumble/machines/peer1/peer_1_test_cert create mode 100644 checks/mumble/machines/peer1/peer_1_test_key create mode 100644 checks/mumble/machines/peer2/facts/mumble-cert create mode 100644 checks/mumble/machines/peer2/peer_2_test_cert create mode 100644 checks/mumble/machines/peer2/peer_2_test_key create mode 100644 checks/mumble/peer_1/key.age create mode 100644 checks/mumble/peer_1/peer_1_test_cert create mode 100644 checks/mumble/peer_1/peer_1_test_key create mode 100644 checks/mumble/peer_2/peer_2_test_cert create mode 100644 checks/mumble/peer_2/peer_2_test_key create mode 100644 clanModules/mumble/README.md create mode 100644 clanModules/mumble/default.nix create mode 100644 clanModules/mumble/mumble-populate-channels.py create mode 100644 clanModules/mumble/test.nix diff --git a/checks/flake-module.nix b/checks/flake-module.nix index 6b591aa55..652b96423 100644 --- a/checks/flake-module.nix +++ b/checks/flake-module.nix @@ -40,10 +40,11 @@ secrets = import ./secrets nixosTestArgs; container = import ./container nixosTestArgs; deltachat = import ./deltachat nixosTestArgs; - matrix-synapse = import ./matrix-synapse nixosTestArgs; - zt-tcp-relay = import ./zt-tcp-relay nixosTestArgs; borgbackup = import ./borgbackup nixosTestArgs; + matrix-synapse = import ./matrix-synapse nixosTestArgs; + mumble = import ./mumble nixosTestArgs; syncthing = import ./syncthing nixosTestArgs; + zt-tcp-relay = import ./zt-tcp-relay nixosTestArgs; postgresql = import ./postgresql nixosTestArgs; wayland-proxy-virtwl = import ./wayland-proxy-virtwl nixosTestArgs; }; diff --git a/checks/mumble/default.nix b/checks/mumble/default.nix new file mode 100644 index 000000000..c2001b98b --- /dev/null +++ b/checks/mumble/default.nix @@ -0,0 +1,150 @@ +(import ../lib/test-base.nix) ( + { ... }: + let + common = + { self, pkgs, ... }: + { + imports = [ + self.clanModules.mumble + self.nixosModules.clanCore + (self.inputs.nixpkgs + "/nixos/tests/common/x11.nix") + { + clan.core.clanDir = ./.; + environment.systemPackages = [ + pkgs.litecli + pkgs.xdotool + pkgs.killall + ]; + services.murmur.sslKey = "/etc/mumble-key"; + services.murmur.sslCert = "/etc/mumble-cert"; + clan.core.facts.services.mumble.secret."mumble-key".path = "/etc/mumble-key"; + clan.core.facts.services.mumble.public."mumble-cert".path = "/etc/mumble-cert"; + } + ]; + + }; + in + { + name = "mumble"; + + enableOCR = true; + + nodes.peer1 = + { ... }: + { + imports = [ + common + { + clan.core.machineName = "peer1"; + environment.etc = { + "mumble-key".source = ./peer_1/peer_1_test_key; + "mumble-cert".source = ./peer_1/peer_1_test_cert; + }; + systemd.tmpfiles.settings."vmsecrets" = { + "/etc/secrets/mumble-key" = { + C.argument = "${./peer_1/peer_1_test_key}"; + z = { + mode = "0400"; + user = "murmur"; + }; + }; + "/etc/secrets/mumble-cert" = { + C.argument = "${./peer_1/peer_1_test_cert}"; + z = { + mode = "0400"; + user = "murmur"; + }; + }; + }; + services.murmur.sslKey = "/etc/mumble-key"; + services.murmur.sslCert = "/etc/mumble-cert"; + clan.core.facts.services.mumble.secret."mumble-key".path = "/etc/mumble-key"; + clan.core.facts.services.mumble.public."mumble-cert".path = "/etc/mumble-cert"; + } + ]; + }; + nodes.peer2 = + { ... }: + { + imports = [ + common + { + clan.core.machineName = "peer2"; + environment.etc = { + "mumble-key".source = ./peer_2/peer_2_test_key; + "mumble-cert".source = ./peer_2/peer_2_test_cert; + }; + systemd.tmpfiles.settings."vmsecrets" = { + "/etc/secrets/mumble-key" = { + C.argument = "${./peer_2/peer_2_test_key}"; + z = { + mode = "0400"; + user = "murmur"; + }; + }; + "/etc/secrets/mumble-cert" = { + C.argument = "${./peer_2/peer_2_test_cert}"; + z = { + mode = "0400"; + user = "murmur"; + }; + }; + }; + } + ]; + }; + testScript = '' + start_all() + + with subtest("Waiting for x"): + peer1.wait_for_x() + peer2.wait_for_x() + + with subtest("Waiting for murmur"): + peer1.wait_for_unit("murmur.service") + peer2.wait_for_unit("murmur.service") + + with subtest("Starting Mumble"): + # starting mumble is blocking + peer1.execute("mumble >&2 &") + peer2.execute("mumble >&2 &") + + with subtest("Wait for Mumble"): + peer1.wait_for_window(r"^Mumble$") + peer2.wait_for_window(r"^Mumble$") + + with subtest("Wait for certificate creation"): + peer1.wait_for_window(r"^Mumble$") + peer1.sleep(3) # mumble is slow to register handlers + peer1.send_chars("\n") + peer1.send_chars("\n") + peer2.wait_for_window(r"^Mumble$") + peer2.sleep(3) # mumble is slow to register handlers + peer2.send_chars("\n") + peer2.send_chars("\n") + + with subtest("Wait for server connect"): + peer1.wait_for_window(r"^Mumble Server Connect$") + peer2.wait_for_window(r"^Mumble Server Connect$") + + with subtest("Check validity of server certificates"): + peer1.execute("killall .mumble-wrapped") + peer1.sleep(1) + peer1.execute("mumble mumble://peer2 >&2 &") + peer1.wait_for_window(r"^Mumble$") + peer1.sleep(3) # mumble is slow to register handlers + peer1.send_chars("\n") + peer1.send_chars("\n") + peer1.wait_for_text("Connected.") + + peer2.execute("killall .mumble-wrapped") + peer2.sleep(1) + peer2.execute("mumble mumble://peer1 >&2 &") + peer2.wait_for_window(r"^Mumble$") + peer2.sleep(3) # mumble is slow to register handlers + peer2.send_chars("\n") + peer2.send_chars("\n") + peer2.wait_for_text("Connected.") + ''; + } +) diff --git a/checks/mumble/machines/peer1/facts/mumble-cert b/checks/mumble/machines/peer1/facts/mumble-cert new file mode 100644 index 000000000..9d8d6c654 --- /dev/null +++ b/checks/mumble/machines/peer1/facts/mumble-cert @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIUCUjfNkF0CDhTKbO3nNczcsCW4qEwDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA2MjcwOTM2NDZaFw0yNDA3 +MjcwOTM2NDZaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDCcdZEJvXJIeOKO5pF5XUFvUeJtCCiwfWvWS662bxc +R/5MZucRLqfTNYo9aBv4NITw5kxZsTaaubmS4zSGQoTEAVzqzVdi3a/gNvsdVLb+ +7CivpmweLllX/OGsTL0kHPEI+74AYiTBjXfdWV1Y5T1tuwc3G8ATrguQ33Uo5vvF +vcqsbTKcRZC0pB9O/nn4q03GsRdvlpaKakIhjMpRG/uZ3u7wtbyZ+WqjsjxZNfnY +aMyPoaipFqX1v+L7GKlOj2NpyEZFVVwa2ZqhVSYXyDfpAWQFznwKGzD5mjtcyKym +gnv/5LwrpH4Xj+JMt48hN+rPnu5vfXT8Y4KnID30OQW7AgMBAAGjUzBRMB0GA1Ud +DgQWBBQBBO8Wp975pAGioMjkaxANAVInfzAfBgNVHSMEGDAWgBQBBO8Wp975pAGi +oMjkaxANAVInfzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAg +F40MszTZXpR/A1z9B1CcXH47tNK67f8bCMR2dhvXODbpatwSihyxhQjtLb5R6kYH +5Yq/B4yrh303j0CXaobCQ4nQH7zI7fhViww+TzW7vDhgM7ueEyyXrqCXt6JY8avg +TuvIRtJSeWSQJ5aLNaYqmiwMf/tj9W3BMDpctGyLqu1WTSrbpYa9mA5Vudud70Yz +DgZ/aqHilB07cVNqzVYZzRZ56WJlTjGzVevRgnHZqPiZNVrU13H6gtWa3r8aV4Gj +i4F663eRAttj166cRgfl1QqpSG2IprNyV9UfuS2LlUaVNT3y0idawiJ4HhaA8pGB +ZqMUUkA4DSucb6xxEcTK +-----END CERTIFICATE----- + diff --git a/checks/mumble/machines/peer1/key.age b/checks/mumble/machines/peer1/key.age new file mode 100644 index 000000000..1c9755ab6 --- /dev/null +++ b/checks/mumble/machines/peer1/key.age @@ -0,0 +1 @@ +AGE-SECRET-KEY-1UCXEUJH6JXF8LFKWFHDM4N9AQE2CCGQZGXLUNV4TKR5KY0KC8FDQ2TY4NX diff --git a/checks/mumble/machines/peer1/peer_1_test_cert b/checks/mumble/machines/peer1/peer_1_test_cert new file mode 100644 index 000000000..effa81269 --- /dev/null +++ b/checks/mumble/machines/peer1/peer_1_test_cert @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICHTCCAaKgAwIBAgIIT2gZuvqVFP0wCgYIKoZIzj0EAwIwSjESMBAGA1UEChMJ +U3luY3RoaW5nMSAwHgYDVQQLExdBdXRvbWF0aWNhbGx5IEdlbmVyYXRlZDESMBAG +A1UEAxMJc3luY3RoaW5nMB4XDTIzMTIwNjAwMDAwMFoXDTQzMTIwMTAwMDAwMFow +SjESMBAGA1UEChMJU3luY3RoaW5nMSAwHgYDVQQLExdBdXRvbWF0aWNhbGx5IEdl +bmVyYXRlZDESMBAGA1UEAxMJc3luY3RoaW5nMHYwEAYHKoZIzj0CAQYFK4EEACID +YgAEBAr1CsciwCa0vi7eC6xxuSGijY3txbjtsyFanec/fge4oJBD3rVpaLKFETb3 +TvHHsuvblzElcP483MEVq6FMUoxwuL9CzTtpJrRhtwSmAs8AHLFu8irVn8sZjgkL +sXMho1UwUzAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG +AQUFBwMCMAwGA1UdEwEB/wQCMAAwFAYDVR0RBA0wC4IJc3luY3RoaW5nMAoGCCqG +SM49BAMCA2kAMGYCMQDbrtLgfcyMMIkNQn+PJe9DHYAqj8C47LQcWuIY/nekhOu0 +aUfKctEAwyBtI60Y5zcCMQCEdgD/6CNBh7Qqq3z3CKPhlrpxHtCO5tNw17k0jfdH +haCwJInHZvZgclHk4EtFpTw= +-----END CERTIFICATE----- diff --git a/checks/mumble/machines/peer1/peer_1_test_key b/checks/mumble/machines/peer1/peer_1_test_key new file mode 100644 index 000000000..101f810c4 --- /dev/null +++ b/checks/mumble/machines/peer1/peer_1_test_key @@ -0,0 +1,6 @@ +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDA14Nqo17Xs/xRLGH2KLuyzjKp4eW9iWFobVNM93RZZbECT++W3XcQc +cEc5WVtiPmWgBwYFK4EEACKhZANiAAQECvUKxyLAJrS+Lt4LrHG5IaKNje3FuO2z +IVqd5z9+B7igkEPetWlosoURNvdO8cey69uXMSVw/jzcwRWroUxSjHC4v0LNO2km +tGG3BKYCzwAcsW7yKtWfyxmOCQuxcyE= +-----END EC PRIVATE KEY----- diff --git a/checks/mumble/machines/peer2/facts/mumble-cert b/checks/mumble/machines/peer2/facts/mumble-cert new file mode 100644 index 000000000..cbbae2413 --- /dev/null +++ b/checks/mumble/machines/peer2/facts/mumble-cert @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIUfENbTtH5nr7giuawwQpDYqUpWJswDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA2MjcwOTQxNDNaFw0yNDA3 +MjcwOTQxNDNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCfP6cZhCs9jOnWqyQP12vrOOxlBrWofYZFf9amUA24 +AfE7oGcSfkylanmkxzvGqQkhgLAvkHZj/GEvHujKyy8PgcEGP+pwmsfWNQMvU0Dz +j3syjWOTi3eIC/3DoUnHlWCT2qCil/bjqxgU1l7fO/OXUlq5kyvIjln7Za4sUHun +ixe/m96Er6l8a4Mh2pxh2C5pkLCvulkQhjjGG+R6MccH8wwQwmLg5oVBkFEZrnRE +pnRKBI0DvA+wk1aJFAPOI4d8Q5T7o/MyxH3f8TYGHqbeMQFCKwusnlWPRtrNdaIc +gaLvSpR0LVlroXGu8tYmRpvHPByoKGDbgVvO0Bwx8fmRAgMBAAGjUzBRMB0GA1Ud +DgQWBBR7r+mQWNUZ0TpQNwrwjgxgngvOjTAfBgNVHSMEGDAWgBR7r+mQWNUZ0TpQ +NwrwjgxgngvOjTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCO +7B4s6uQEGE8jg3CQgy76oU/D8sazGcP8+/E4JLHSc0Nj49w4ztSpkOVk2HyEtzbm +uR3TreIw+SfqpbiOI/ivVNDbEBsb/vEeq7qPzDH1Bi72plHZNRVhNGGV5rd7ibga +TkfXHKPM9yt8ffffHHiu1ROvb8gg2B6JbQwboU4hvvmmorW7onyTFSYEzZVdNSpv +pUtKPldxYjTnLlbsJdXC4xyCC4PrJt2CC0n0jsWfICJ77LMxIxTODh8oZNjbPg6r +RdI7U/DsD+R072DjbIcrivvigotJM+jihzz5inZwbO8o0WQOHAbJLIG3C3BnRW3A +Ek4u3+HXZMl5a0LGJ76u +-----END CERTIFICATE----- + diff --git a/checks/mumble/machines/peer2/peer_2_test_cert b/checks/mumble/machines/peer2/peer_2_test_cert new file mode 100644 index 000000000..b0830f0ef --- /dev/null +++ b/checks/mumble/machines/peer2/peer_2_test_cert @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICHjCCAaOgAwIBAgIJAKbMWefkf1rVMAoGCCqGSM49BAMCMEoxEjAQBgNVBAoT +CVN5bmN0aGluZzEgMB4GA1UECxMXQXV0b21hdGljYWxseSBHZW5lcmF0ZWQxEjAQ +BgNVBAMTCXN5bmN0aGluZzAeFw0yMzEyMDYwMDAwMDBaFw00MzEyMDEwMDAwMDBa +MEoxEjAQBgNVBAoTCVN5bmN0aGluZzEgMB4GA1UECxMXQXV0b21hdGljYWxseSBH +ZW5lcmF0ZWQxEjAQBgNVBAMTCXN5bmN0aGluZzB2MBAGByqGSM49AgEGBSuBBAAi +A2IABFZTMt4RfsfBue0va7QuNdjfXMI4HfZzJCEcG+b9MtV7FlDmwMKX5fgGykD9 +FBbC7yiza3+xCobdMb5bakz1qYJ7nUFCv1mwSDo2eNM+/XE+rJmlre8NwkwGmvzl +h1uhyqNVMFMwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr +BgEFBQcDAjAMBgNVHRMBAf8EAjAAMBQGA1UdEQQNMAuCCXN5bmN0aGluZzAKBggq +hkjOPQQDAgNpADBmAjEAwzhsroN6R4/quWeXj6dO5gt5CfSTLkLee6vrcuIP5i1U +rZvJ3OKQVmmGG6IWYe7iAjEAyuq3X2wznaqiw2YK3IDI4qVeYWpCUap0fwRNq7/x +4dC4k+BOzHcuJOwNBIY/bEuK +-----END CERTIFICATE----- diff --git a/checks/mumble/machines/peer2/peer_2_test_key b/checks/mumble/machines/peer2/peer_2_test_key new file mode 100644 index 000000000..7b9b28a04 --- /dev/null +++ b/checks/mumble/machines/peer2/peer_2_test_key @@ -0,0 +1,6 @@ +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDCXHGpvumKjjDRxB6SsjZOb7duw3w+rdlGQCJTIvRThLjD6zwjnyImi +7c3PD5nWtLqgBwYFK4EEACKhZANiAARWUzLeEX7HwbntL2u0LjXY31zCOB32cyQh +HBvm/TLVexZQ5sDCl+X4BspA/RQWwu8os2t/sQqG3TG+W2pM9amCe51BQr9ZsEg6 +NnjTPv1xPqyZpa3vDcJMBpr85Ydboco= +-----END EC PRIVATE KEY----- diff --git a/checks/mumble/peer_1/key.age b/checks/mumble/peer_1/key.age new file mode 100644 index 000000000..1c9755ab6 --- /dev/null +++ b/checks/mumble/peer_1/key.age @@ -0,0 +1 @@ +AGE-SECRET-KEY-1UCXEUJH6JXF8LFKWFHDM4N9AQE2CCGQZGXLUNV4TKR5KY0KC8FDQ2TY4NX diff --git a/checks/mumble/peer_1/peer_1_test_cert b/checks/mumble/peer_1/peer_1_test_cert new file mode 100644 index 000000000..9d8d6c654 --- /dev/null +++ b/checks/mumble/peer_1/peer_1_test_cert @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIUCUjfNkF0CDhTKbO3nNczcsCW4qEwDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA2MjcwOTM2NDZaFw0yNDA3 +MjcwOTM2NDZaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDCcdZEJvXJIeOKO5pF5XUFvUeJtCCiwfWvWS662bxc +R/5MZucRLqfTNYo9aBv4NITw5kxZsTaaubmS4zSGQoTEAVzqzVdi3a/gNvsdVLb+ +7CivpmweLllX/OGsTL0kHPEI+74AYiTBjXfdWV1Y5T1tuwc3G8ATrguQ33Uo5vvF +vcqsbTKcRZC0pB9O/nn4q03GsRdvlpaKakIhjMpRG/uZ3u7wtbyZ+WqjsjxZNfnY +aMyPoaipFqX1v+L7GKlOj2NpyEZFVVwa2ZqhVSYXyDfpAWQFznwKGzD5mjtcyKym +gnv/5LwrpH4Xj+JMt48hN+rPnu5vfXT8Y4KnID30OQW7AgMBAAGjUzBRMB0GA1Ud +DgQWBBQBBO8Wp975pAGioMjkaxANAVInfzAfBgNVHSMEGDAWgBQBBO8Wp975pAGi +oMjkaxANAVInfzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAg +F40MszTZXpR/A1z9B1CcXH47tNK67f8bCMR2dhvXODbpatwSihyxhQjtLb5R6kYH +5Yq/B4yrh303j0CXaobCQ4nQH7zI7fhViww+TzW7vDhgM7ueEyyXrqCXt6JY8avg +TuvIRtJSeWSQJ5aLNaYqmiwMf/tj9W3BMDpctGyLqu1WTSrbpYa9mA5Vudud70Yz +DgZ/aqHilB07cVNqzVYZzRZ56WJlTjGzVevRgnHZqPiZNVrU13H6gtWa3r8aV4Gj +i4F663eRAttj166cRgfl1QqpSG2IprNyV9UfuS2LlUaVNT3y0idawiJ4HhaA8pGB +ZqMUUkA4DSucb6xxEcTK +-----END CERTIFICATE----- + diff --git a/checks/mumble/peer_1/peer_1_test_key b/checks/mumble/peer_1/peer_1_test_key new file mode 100644 index 000000000..c52c49f27 --- /dev/null +++ b/checks/mumble/peer_1/peer_1_test_key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDCcdZEJvXJIeOK +O5pF5XUFvUeJtCCiwfWvWS662bxcR/5MZucRLqfTNYo9aBv4NITw5kxZsTaaubmS +4zSGQoTEAVzqzVdi3a/gNvsdVLb+7CivpmweLllX/OGsTL0kHPEI+74AYiTBjXfd +WV1Y5T1tuwc3G8ATrguQ33Uo5vvFvcqsbTKcRZC0pB9O/nn4q03GsRdvlpaKakIh +jMpRG/uZ3u7wtbyZ+WqjsjxZNfnYaMyPoaipFqX1v+L7GKlOj2NpyEZFVVwa2Zqh +VSYXyDfpAWQFznwKGzD5mjtcyKymgnv/5LwrpH4Xj+JMt48hN+rPnu5vfXT8Y4Kn +ID30OQW7AgMBAAECggEAGVKn+/Iy+kG+l2cRvV6XseqnoWhjA69M5swviMgIfuAl +Xx/boeI4mwoS+dJQKi/0zEbB1MB+gwIDB/0s/vs0vS4MQswBQG/skr+2TmiU+Hgb +CF0dIYUZv5rAbScFTumx/mCCqxwc+1QIMzyLKqOYL203EFc92ZJGEVT4th321haZ +8Wd+dllcYAb7BbEeBhCrTqRe9T3zt5reZgtZTquTF5hGm8EAyBp6rLjZK7dyZ9dd +gyIsDbWgPC9vkRc6x/eANn70hgDbYOuoXwAP/qIFnWLL1Zzy8LKUyOsSgQ91S3S3 +Il4Lt6lEyU3+61MsCYss7jDoP/7REEjz5h6gfxlFSQKBgQD9u8nhHuwte4/d9VNU +rhSBW9h8IJzwPif/eS8vh9VaS2SjR2dDCcHg6rGYKnexeEzUcx56aQMA+p3nRJwy +Uwnx5BfEWs9FO6yPR8VEI0a2sBp+hoWKJX/Lvat+QCs6IFuGmlQpczD7/RYAkhG4 +mwyt/ymqzjukb9mFaeYIltOfPwKBgQDELnkH1ChTUH5u3HgDoelFbzR18okz6dxH +urMbfZMAl8W5h2zAvHsAX5qxyHHankOUsiH2y3BrAgqQtTuIA2a5W7j+yHBkYiEZ +EUNeI9YNA0KU+wwZpVVvRGUsRB5SUBo5LlcSYmX/V32f0oU5Np44i0vjl3Ju8esx +2MLfj1A2hQKBgQDCxtZZZ0h8Pb8Z7wpSFfQNvXi5CLwQvFYuClQLk6VXVErkAJsn +XiUjyGYeXnNVm/i2mcyKwXQZ20k90HBrPU2ED8mi5Ob5ya5Uqw6mmMHe2d7sw81d +WB37RBWSrCXC0DYSZQQ4cYHn3sd2Fqtd4EBijV7qDLjCKU582OdKLqYzNwKBgH31 +UKQkJZgIkIThbPT4GewI0GgCRvFb76DmUGUQJTg2Oi86siq1WUwOFiabie5RuxZX +oNLyH8W008/BbO2RMX1FVOvRCciJ8LJFkTl6TM6iDzfUUBqPOuFryoG3Yrh60btw +81rMbqyZIgFhi0QGu2OWnC0Oadyt2tJwV/5t55R5AoGBAPspZttDmOzVkAJDSn9Z +iByYt1KmwBQ6l7LpFg33a7ds9zWqW4+i6r0PzXvSewf/z69L0cAywSk5CaJJjDso +dTlNMqwux01wd6V+nQGR871xnsOg+qzgJ565TJZelWgRmNRUooi4DMp5POJA33xp +rqAISUfW0w2S+q7/5Lm0QiJE +-----END PRIVATE KEY----- diff --git a/checks/mumble/peer_2/peer_2_test_cert b/checks/mumble/peer_2/peer_2_test_cert new file mode 100644 index 000000000..cbbae2413 --- /dev/null +++ b/checks/mumble/peer_2/peer_2_test_cert @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIUfENbTtH5nr7giuawwQpDYqUpWJswDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA2MjcwOTQxNDNaFw0yNDA3 +MjcwOTQxNDNaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCfP6cZhCs9jOnWqyQP12vrOOxlBrWofYZFf9amUA24 +AfE7oGcSfkylanmkxzvGqQkhgLAvkHZj/GEvHujKyy8PgcEGP+pwmsfWNQMvU0Dz +j3syjWOTi3eIC/3DoUnHlWCT2qCil/bjqxgU1l7fO/OXUlq5kyvIjln7Za4sUHun +ixe/m96Er6l8a4Mh2pxh2C5pkLCvulkQhjjGG+R6MccH8wwQwmLg5oVBkFEZrnRE +pnRKBI0DvA+wk1aJFAPOI4d8Q5T7o/MyxH3f8TYGHqbeMQFCKwusnlWPRtrNdaIc +gaLvSpR0LVlroXGu8tYmRpvHPByoKGDbgVvO0Bwx8fmRAgMBAAGjUzBRMB0GA1Ud +DgQWBBR7r+mQWNUZ0TpQNwrwjgxgngvOjTAfBgNVHSMEGDAWgBR7r+mQWNUZ0TpQ +NwrwjgxgngvOjTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCO +7B4s6uQEGE8jg3CQgy76oU/D8sazGcP8+/E4JLHSc0Nj49w4ztSpkOVk2HyEtzbm +uR3TreIw+SfqpbiOI/ivVNDbEBsb/vEeq7qPzDH1Bi72plHZNRVhNGGV5rd7ibga +TkfXHKPM9yt8ffffHHiu1ROvb8gg2B6JbQwboU4hvvmmorW7onyTFSYEzZVdNSpv +pUtKPldxYjTnLlbsJdXC4xyCC4PrJt2CC0n0jsWfICJ77LMxIxTODh8oZNjbPg6r +RdI7U/DsD+R072DjbIcrivvigotJM+jihzz5inZwbO8o0WQOHAbJLIG3C3BnRW3A +Ek4u3+HXZMl5a0LGJ76u +-----END CERTIFICATE----- + diff --git a/checks/mumble/peer_2/peer_2_test_key b/checks/mumble/peer_2/peer_2_test_key new file mode 100644 index 000000000..fe022bdd6 --- /dev/null +++ b/checks/mumble/peer_2/peer_2_test_key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCfP6cZhCs9jOnW +qyQP12vrOOxlBrWofYZFf9amUA24AfE7oGcSfkylanmkxzvGqQkhgLAvkHZj/GEv +HujKyy8PgcEGP+pwmsfWNQMvU0Dzj3syjWOTi3eIC/3DoUnHlWCT2qCil/bjqxgU +1l7fO/OXUlq5kyvIjln7Za4sUHunixe/m96Er6l8a4Mh2pxh2C5pkLCvulkQhjjG +G+R6MccH8wwQwmLg5oVBkFEZrnREpnRKBI0DvA+wk1aJFAPOI4d8Q5T7o/MyxH3f +8TYGHqbeMQFCKwusnlWPRtrNdaIcgaLvSpR0LVlroXGu8tYmRpvHPByoKGDbgVvO +0Bwx8fmRAgMBAAECggEACAkjOnNj5zA0IIP0RuRc6rqtmw9ynTTwUJN51lyVxKI8 +dQDMEq/S2En+J2VyS7z92/XtbgkBIFx83u7VWl5UWpj2j4UsJFB7IwD7zyiJT4D+ ++3cM/kX8Wx4XyQZbfbm47N0MXAgFCkn45hxHH0acLReXwmN9wxoDyl7AIjZRdwvG +Qq0rnOnIc8kkkew7L6AiFwQS8b77eyzua3d6moKXN9hU/kfiJ6YUFG/WLe0pmQA1 +HbF27YghfeLnYUt50oDuX6jF6CzQhflchWVq/wn8/cxEpg/RMicWE8ulrTk7o27l +JwCrHrhYEBsPuZO4mxX/DHrAMmhTeFjLaV5bQlz0PQKBgQDgRPSOEixYnKz9iPs/ +EDTlji5LA3Rm6TytRCNsjYY6Trw60KcvYqwyDUCiEjruvOQ9mqgBiQm1VHSalrG3 +RcbVfpEMouyZbEwmTjS8KdOi5x4Z6AX+4yWDN31jX3b8sktgbxV/HRdg3sA3q7MJ +vExTUuoXg57W+FepIZ+XlhSoQwKBgQC1x6UMAlAeW45/yUUm/LFRcCgb/bdCQx+e +hSb8w3jdvVoNWgx1j7RsjjFKaZUnseK3qQvVfCm4Qjvlz6MpKDxslaUYuR162Ku0 +e153z/xc7XRoXyPyPLdGZFlWii30jirB7ZqPdyz6mwlWwqdImNerbUqdFt9R8bId +pYsyHB5zmwKBgBjYCq9iW/9E+/TqI8sMpI95fK9app5v4AThs3rnAqOa7Ucmrh6V +s7Wnui06D8U6r54Tb+EbqTOpM3Gcl/tRg4FLEA5yTfuA/76Ok1D04Tj+mVsNVPyz +dQhgMUe835WGusroA12df2V/x5NjNeYyMdJZMQ2ByyrNQAjAbMmCGq+5AoGBAIj8 +ERFysMOfxUvg9b7CkDFJrsAhOzew86P2vYGfIHchGTqUkG0LRTDFGrnzxNXsBGjY ++DUB40Kajx7IkTETxC0jvA1ceq23l/VjPrZVQt0YiC+a+rCyNn7SYkyHxsfTVr9b +ea0BZyDXMntyJrPbkjL6Ik8tDE9pLwuOU84ISJ5fAoGAZ2+Ams/VhdZj/wpRpMky +K4jtS4nzbCmJzzTa6vdVV7Kjer5kFxSFFqMrS/FtJ/RxHeHvxdze9dfGu9jIdTKK +vSzbyQdHFfZgRkmAKfcoN9u567z7Oc74AQ9UgFEGdEVFQUbfWOevmr8KIPt8nDQK +J9HuVfILi1kH0jzDd/64TvA= +-----END PRIVATE KEY----- diff --git a/clanModules/flake-module.nix b/clanModules/flake-module.nix index a5601ca83..f385b87f3 100644 --- a/clanModules/flake-module.nix +++ b/clanModules/flake-module.nix @@ -11,6 +11,7 @@ matrix-synapse = ./matrix-synapse; moonlight = ./moonlight; packages = ./packages; + mumble = ./mumble; postgresql = ./postgresql; root-password = ./root-password; sshd = ./sshd; diff --git a/clanModules/mumble/README.md b/clanModules/mumble/README.md new file mode 100644 index 000000000..38b62f239 --- /dev/null +++ b/clanModules/mumble/README.md @@ -0,0 +1,14 @@ +--- +description = "Open Source, Low Latency, High Quality Voice Chat." +categories = ["chat", "voice"] +--- +The mumble clan module gives you: + +- True low latency voice communication. +- Secure, authenticated encryption. +- Free software. +- Backed by a large and active open-source community. + +This all set up in a way that allows peer-to-peer hosting. +Every machine inside the clan can be a host for mumble, +and thus it doesn't matter who in the network is online - as long as two people are online they are able to chat with each other. diff --git a/clanModules/mumble/default.nix b/clanModules/mumble/default.nix new file mode 100644 index 000000000..785cf6d4f --- /dev/null +++ b/clanModules/mumble/default.nix @@ -0,0 +1,105 @@ +{ + lib, + config, + pkgs, + ... +}: +let + clanDir = lib.trace config.clan.core.clanDir config.clan.core.clanDir; + machineDir = clanDir + "/machines/"; + machinesFileSet = builtins.readDir machineDir; + machines = lib.mapAttrsToList (name: _: name) machinesFileSet; + machineJson = builtins.toJSON (lib.trace machines machines); + certificateMachinePath = machines: machineDir + "/${machines}" + "/facts/mumble-cert"; + certificatesUnchecked = builtins.map ( + machine: + let + fullPath = certificateMachinePath machine; + in + if builtins.pathExists (lib.trace fullPath fullPath) then machine else null + ) machines; + certificate = lib.filter (machine: machine != null) certificatesUnchecked; + machineCert = builtins.map ( + machine: + lib.trace machine (lib.nameValuePair machine (builtins.readFile (certificateMachinePath machine))) + ) certificate; + machineCertJson = builtins.toJSON (lib.trace machineCert machineCert); + +in +{ + options.clan.services.mumble = { + user = lib.mkOption { + type = lib.types.string; + default = "alice"; + description = "The user mumble should be set up for."; + }; + }; + + config = { + services.murmur = { + enable = true; + logDays = -1; + registerName = config.clan.core.machineName; + openFirewall = true; + bonjour = true; + sslKey = config.clan.core.facts.services.mumble.secret.mumble-key.path; + sslCert = config.clan.core.facts.services.mumble.public.mumble-cert.path; + }; + + clan.core.state.mumble.folders = [ + "/var/lib/mumble" + "/var/lib/murmur" + ]; + + systemd.tmpfiles.rules = [ + "d '/var/lib/mumble' 0770 '${config.clan.services.mumble.user}' 'users' - -" + ]; + + environment.systemPackages = + let + mumbleCfgDir = "/var/lib/mumble"; + mumbleDatabasePath = "${mumbleCfgDir}/mumble.sqlite"; + mumbleCfgPath = "/var/lib/mumble/mumble_settings.json"; + populate-channels = pkgs.writers.writePython3 "mumble-populate-channels" { + libraries = [ + pkgs.python3Packages.cryptography + pkgs.python3Packages.pyopenssl + ]; + flakeIgnore = [ + # We don't live in the dark ages anymore. + # Languages like Python that are whitespace heavy will overrun + # 79 characters.. + "E501" + ]; + } (builtins.readFile ./mumble-populate-channels.py); + mumble = pkgs.writeShellScriptBin "mumble" '' + set -xeu + mkdir -p ${mumbleCfgDir} + pushd "${mumbleCfgDir}" + XDG_DATA_HOME=${mumbleCfgDir} + XDG_DATA_DIR=${mumbleCfgDir} + ${populate-channels} --ensure-config '${mumbleCfgPath}' --db-location ${mumbleDatabasePath} + echo ${machineCertJson} + ${populate-channels} --machines '${machineJson}' --username ${config.clan.core.machineName} --db-location ${mumbleDatabasePath} + ${populate-channels} --servers '${machineCertJson}' --username ${config.clan.core.machineName} --db-location ${mumbleDatabasePath} --cert True + ${pkgs.mumble}/bin/mumble --config ${mumbleCfgPath} "$@" + popd + ''; + in + [ mumble ]; + + clan.core.facts.services.mumble = { + secret.mumble-key = { }; + public.mumble-cert = { }; + generator.path = [ + pkgs.coreutils + pkgs.openssl + ]; + generator.script = '' + openssl genrsa -out $secrets/mumble-key 2048 + openssl req -new -x509 -key $secrets/mumble-key -out $facts/mumble-cert + ''; + }; + }; + +} diff --git a/clanModules/mumble/mumble-populate-channels.py b/clanModules/mumble/mumble-populate-channels.py new file mode 100644 index 000000000..56ee36668 --- /dev/null +++ b/clanModules/mumble/mumble-populate-channels.py @@ -0,0 +1,249 @@ +import argparse +import json +import os +import sqlite3 + + +def ensure_config(path: str, db_path: str) -> None: + # Default JSON structure if the file doesn't exist + default_json = { + "misc": { + "audio_wizard_has_been_shown": True, + "database_location": db_path, + "viewed_server_ping_consent_message": True, + }, + "settings_version": 1, + } + + # Check if the file exists + if os.path.exists(path): + with open(path) as file: + data = json.load(file) + else: + data = default_json + # Create the file with default JSON structure + with open(path, "w") as file: + json.dump(data, file, indent=4) + + # TODO: make sure to only update the diff + updated_data = {**default_json, **data} + + # Write the modified JSON object back to the file + with open(path, "w") as file: + json.dump(updated_data, file, indent=4) + + +def initialize_database(db_location: str) -> None: + """ + Initializes the database. If the database or the servers table does not exist, it creates them. + + :param db_location: The path to the SQLite database + """ + conn = sqlite3.connect(db_location) + try: + cursor = conn.cursor() + + # Create the servers table if it doesn't exist + cursor.execute(""" + CREATE TABLE IF NOT EXISTS servers ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + hostname TEXT NOT NULL, + port INTEGER NOT NULL, + username TEXT NOT NULL, + password TEXT NOT NULL, + url TEXT + ) + """) + + # Commit the changes + conn.commit() + + except sqlite3.Error as e: + print(f"An error occurred while initializing the database: {e}") + finally: + conn.close() + + +def initialize_certificates( + db_location: str, hostname: str, port: str, digest: str +) -> None: + # Connect to the SQLite database + conn = sqlite3.connect(db_location) + + try: + # Create a cursor object + cursor = conn.cursor() + + # TODO: check if cert already there + # if server_check(cursor, name, hostname): + # print( + # f"Server with name '{name}' and hostname '{hostname}' already exists." + # ) + # return + + # SQL command to insert data into the servers table + insert_query = """ + INSERT INTO cert (hostname, port, digest) + VALUES (?, ?, ?) + """ + + # Data to be inserted + data = (hostname, port, digest) + + # Execute the insert command with the provided data + cursor.execute(insert_query, data) + + # Commit the changes + conn.commit() + + print("Data has been successfully inserted.") + except sqlite3.Error as e: + print(f"An error occurred: {e}") + finally: + # Close the connection + conn.close() + pass + + +def calculate_digest(cert: str) -> str: + from cryptography import x509 + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes + + cert = cert.strip() + cert = cert.encode("utf-8") + cert = x509.load_pem_x509_certificate(cert, default_backend()) + digest = cert.fingerprint(hashes.SHA1()).hex() + return digest + + +def server_check(cursor: str, name: str, hostname: str) -> bool: + """ + Check if a server with the given name and hostname already exists. + + :param cursor: The database cursor + :param name: The name of the server + :param hostname: The hostname of the server + :return: True if the server exists, False otherwise + """ + check_query = """ + SELECT 1 FROM servers WHERE name = ? AND hostname = ? + """ + cursor.execute(check_query, (name, hostname)) + return cursor.fetchone() is not None + + +def insert_server( + name: str, + hostname: str, + port: str, + username: str, + password: str, + url: str, + db_location: str, +) -> None: + """ + Inserts a new server record into the servers table. + + :param name: The name of the server + :param hostname: The hostname of the server + :param port: The port number + :param username: The username + :param password: The password + :param url: The URL + """ + # Connect to the SQLite database + conn = sqlite3.connect(db_location) + + try: + # Create a cursor object + cursor = conn.cursor() + + if server_check(cursor, name, hostname): + print( + f"Server with name '{name}' and hostname '{hostname}' already exists." + ) + return + + # SQL command to insert data into the servers table + insert_query = """ + INSERT INTO servers (name, hostname, port, username, password, url) + VALUES (?, ?, ?, ?, ?, ?) + """ + + # Data to be inserted + data = (name, hostname, port, username, password, url) + + # Execute the insert command with the provided data + cursor.execute(insert_query, data) + + # Commit the changes + conn.commit() + + print("Data has been successfully inserted.") + except sqlite3.Error as e: + print(f"An error occurred: {e}") + finally: + # Close the connection + conn.close() + + +if __name__ == "__main__": + port = 64738 + password = "" + url = None + + parser = argparse.ArgumentParser( + prog="initialize_mumble", + ) + + subparser = parser.add_subparsers(dest="certificates") + # cert_parser = subparser.add_parser("certificates") + + parser.add_argument("--cert") + parser.add_argument("--digest") + parser.add_argument("--machines") + parser.add_argument("--servers") + parser.add_argument("--username") + parser.add_argument("--db-location") + parser.add_argument("--ensure-config") + args = parser.parse_args() + + print(args) + + if args.ensure_config: + ensure_config(args.ensure_config, args.db_location) + print("Initialized config") + exit(0) + + if args.servers: + print(args.servers) + servers = json.loads(f"{args.servers}") + db_location = args.db_location + for server in servers: + digest = calculate_digest(server.get("value")) + name = server.get("name") + initialize_certificates(db_location, name, port, digest) + print("Initialized certificates") + exit(0) + + initialize_database(args.db_location) + + # Insert the server into the database + print(args.machines) + machines = json.loads(f"{args.machines}") + print(machines) + print(list(machines)) + + for machine in list(machines): + print(f"Inserting {machine}.") + insert_server( + machine, + machine, + port, + args.username, + password, + url, + args.db_location, + ) diff --git a/clanModules/mumble/test.nix b/clanModules/mumble/test.nix new file mode 100644 index 000000000..d2d115810 --- /dev/null +++ b/clanModules/mumble/test.nix @@ -0,0 +1,42 @@ +{ pkgs, self, ... }: +pkgs.nixosTest { + name = "mumble"; + nodes.peer1 = + { ... }: + { + imports = [ + self.nixosModules.mumble + self.inputs.clan-core.nixosModules.clanCore + { + config = { + clan.core.machineName = "peer1"; + clan.core.clanDir = ./.; + + documentation.enable = false; + }; + } + ]; + }; + nodes.peer2 = + { ... }: + { + imports = [ + self.nixosModules.mumble + self.inputs.clan-core.nixosModules.clanCore + { + config = { + + clan.core.machineName = "peer2"; + clan.core.clanDir = ./.; + + documentation.enable = false; + }; + } + ]; + }; + + testScript = '' + start_all() + ''; + +} diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 8bd5d22ac..6f506f645 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -73,6 +73,7 @@ nav: - reference/clanModules/syncthing-static-peers.md - reference/clanModules/syncthing.md - reference/clanModules/thelounge.md + - reference/clanModules/mumble.md - reference/clanModules/trusted-nix-caches.md - reference/clanModules/user-password.md - reference/clanModules/xfce.md