From 1ced6b7a3b8c982faf363f85212c96bdc190696a Mon Sep 17 00:00:00 2001 From: Anish Lakhwara Date: Sat, 9 Jan 2021 10:18:53 +0000 Subject: [PATCH] helix --- machines/helix.nix | 52 +++++++++ matrix-slack.nix | 153 +++++++++++++++++++++++++ matrix.nix | 258 +++++++++++++++++++++++++++++++++++++++++++ mautrix-whatsapp.nix | 96 ++++++++++++++++ monitoring.nix | 17 +++ sealight.nix | 14 +++ wireguard.nix | 27 +++++ 7 files changed, 617 insertions(+) create mode 100644 machines/helix.nix create mode 100644 matrix-slack.nix create mode 100644 matrix.nix create mode 100644 mautrix-whatsapp.nix create mode 100644 monitoring.nix create mode 100644 sealight.nix create mode 100644 wireguard.nix diff --git a/machines/helix.nix b/machines/helix.nix new file mode 100644 index 00000000..88fe3172 --- /dev/null +++ b/machines/helix.nix @@ -0,0 +1,52 @@ +{ config, pkgs, ... }: +{ + imports = + [ # Include the results of the hardware scan. + ./hardware-configuration.nix + ./wireguard.nix + ./matrix.nix + ]; + + # Use the GRUB 2 boot loader. + boot.loader.grub.enable = true; + boot.loader.grub.version = 2; + boot.loader.grub.device = "/dev/vda"; # or "nodev" for efi only + + networking.hostName = "helix"; # Define your hostname. + + networking.useDHCP = false; + networking.interfaces.ens3.useDHCP = true; + + # Configure network proxy if necessary + # networking.proxy.default = "http://user:password@proxy:port/"; + # networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain"; + + # Enable the OpenSSH daemon. + services.openssh.enable = true; + #services.openssh.passwordAuthentication = false; + services.openssh.permitRootLogin = "no"; + + # Open ports in the firewall. + # networking.firewall.allowedTCPPorts = [ ... ]; + # networking.firewall.allowedUDPPorts = [ ... ]; + # Or disable the firewall altogether. + # networking.firewall.enable = false; + + # Define a user account. Don't forget to set a password with ‘passwd’. + users.users.anish = { + isNormalUser = true; + extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user + openssh.authorizedKeys.keys = [ + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDM8kTdMGGmr6mv/zv+K9qVjhzqwivyyN5IV04R7/kcWKw1c6ZEzzqqmsnJ/hci6IIxZ/VEUzMhYz0iBCL0wJHHMxYoNCgISWePtDlVJdyrzm9OWjJNcRStZBW3XcY9dELJsCVcQ4LAVYE4vA+MdmbwM2FZgJhAH6qUzujpfNrj/YIVG7MTN6tbUd63uZF57F43c8WUjgRJx2o7Wl9bSe6v3PZ4fAgEO+wQPso+W+pEXLZ3hBNrHdybwdoGvG9tHlBDbLid11BxtpzmwR9CydDfpbyOXB2+tJkkNXhsHB22mN/2+vGWYhDKYPTFqbDW7McDfQChySyYDdZWmg5WHIGz anish@curve" + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCeWQsf9rbE+KBrWLkLkg1PMKx0jPfDP8xyv+XtddIQzAskRTN+JSSQp1GpoRgfQij7bx6f6DfjkwSQK5NWDQNQ+klGhcdEwEdptWUzpRAvKHMImjyIwFHmRKKEgcfH8+DSHVM93nRSQKrLezX4PzPkHF9f3TLxFdGeQDeY0J2oP2Oa97hRtuTJZW9BwUojyvDYUbg7F3fxf+pdFCvIAFaayiLkhOYdTXnjLt1Ks6NOhSzM78ZibHv4pFYI9r6dhflkFP/SzyLeSSm1aVAtxqE7vF5HnnORAg0byDsiOv/9RXxiDx7gkAAw3xENDSpp9TRj54M7iz1OSsRMhmD6orHjZFIRejlE4DIn+PBPm3PH+U5ILKTogE6n5MfPT28V0QdtHuqq3b5tSWiDcFpnjxbIUmEIo5hLzMR0tXZoNLQhBCSNfxjE8CYzpk7mHbvhSs2GPfG+NI1vvldbw2EF8beJFQHNYTqJw8y7p8RXpCwZqqpSitJX6v9DiYdLVG1hWvE= nix-on-droid@localhost" + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKN8/SH55DBiwVoSnTU8k2Pen+wmovL9QaMyehxGEsJJv/8fzwsswGalD4C/4O51LOvdu4UKkZW5hG02uVSK+58p3UV3pOPyoqsu/aDeIsWsqmTeTzUrBIWOlNzcDKnohLz2oGC5YO+wyTJ9Iteq6aGJDjErsW7sG3h5lXCs551EmJNNGhtBQaaoytMNnWqSdlVjDNCijurH7WUpp40U/RjEp532l4rX6eIIj3jBKEFbhZkFSSjqbj4xM4SyFt+Jmigb1RMjsQjmpfY1vDtM84RcYfpTUte/T5w2dkD5H6kccmWnwKSJpm9wXfx4E7lR9APdUGnau2U1+XxiD3ytGl anish@box" ]; + }; + + # This value determines the NixOS release with which your system is to be + # compatible, in order to avoid breaking some software such as database + # servers. You should change this only after NixOS release notes say you + # should. + system.stateVersion = "19.09"; # Did you read the comment? + +} + diff --git a/matrix-slack.nix b/matrix-slack.nix new file mode 100644 index 00000000..b3b071c2 --- /dev/null +++ b/matrix-slack.nix @@ -0,0 +1,153 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + dataDir = "/var/lib/matrix-appservice-slack"; + registrationFile = "${dataDir}/slack-registration.yaml"; + #appDir = "${pkgs.matrix-appservice-slack}/${pkgs.matrix-appservice-slack.passthru.nodeAppDir}"; + cfg = config.services.matrix-appservice-slack; + # TODO: switch to configGen.json once RFC42 is implemented + settingsFile = pkgs.writeText "matrix-appservice-slack-settings.json" (builtins.toJSON cfg.settings); + +in { + options = { + services.matrix-appservice-slack = { + enable = mkEnableOption "a bridge between Matrix and Slack"; + + settings = mkOption rec { + # TODO: switch to types.config.json as prescribed by RFC42 once it's implemented + type = types.attrs; + apply = recursiveUpdate default; + default = { + # empty values necessary for registration file generation + # actual values defined in environmentFile + auth = { + clientID = ""; + botToken = ""; + }; + }; + example = literalExample '' + { + bridge = { + domain = "public-domain.tld"; + homeserverUrl = "http://public-domain.tld:8008"; + }; + } + ''; + description = '' + config.yaml configuration as a Nix attribute set. + + + + Configuration options should match those described in + + config.sample.yaml. + + + + and + should be set to match the public host name of the Matrix homeserver for webhooks and avatars to work. + + + + Secret tokens should be specified using + instead of this world-readable attribute set. + ''; + }; + + environmentFile = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + File containing environment variables to be passed to the matrix-appservice-discord service, + in which secret tokens can be specified securely by defining values for + APPSERVICE_SLACK_AUTH_CLIENT_I_D and + APPSERVICE_SLACK_AUTH_BOT_TOKEN. + ''; + }; + + url = mkOption { + type = types.str; + default = "http://localhost:${toString cfg.port}"; + description = '' + The URL where the application service is listening for HS requests. + ''; + }; + + port = mkOption { + type = types.port; + default = 9898; # from https://github.com/matrix-org/matrix-appservice-slack/blob/develop/config/config.sample.yaml#L70 + description = '' + Port number on which the bridge should listen for internal communication with the Matrix homeserver. + ''; + }; + + localpart = mkOption { + type = with types; nullOr str; + default = null; + description = '' + The user_id localpart to assign to the AS. + ''; + }; + + serviceDependencies = mkOption { + type = with types; listOf str; + default = optional config.services.matrix-synapse.enable "matrix-synapse.service"; + description = '' + List of Systemd services to require and wait for when starting the application service, + such as the Matrix homeserver if it's running on the same host. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.matrix-appservice-slack = { + description = "A bridge between Matrix and Slack."; + + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ] ++ cfg.serviceDependencies; + after = [ "network-online.target" ] ++ cfg.serviceDependencies; + + preStart = '' + if [ ! -f '${registrationFile}' ]; then + ${pkgs.matrix-appservice-slack}/bin/matrix-appservice-slack \ + --generate-registration \ + --url=${escapeShellArg cfg.url} \ + ${optionalString (cfg.localpart != null) "--localpart=${escapeShellArg cfg.localpart}"} \ + --config='${settingsFile}' \ + --file='${registrationFile}' + fi + ''; + + serviceConfig = { + Type = "simple"; + Restart = "always"; + + ProtectSystem = "strict"; + ProtectHome = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + + DynamicUser = true; + PrivateTmp = true; + #WorkingDirectory = appDir; + StateDirectory = baseNameOf dataDir; + UMask = 0027; + EnvironmentFile = cfg.environmentFile; + + ExecStart = '' + ${pkgs.matrix-appservice-slack}/bin/matrix-appservice-slack \ + --file='${registrationFile}' \ + --config='${settingsFile}' \ + --port='${toString cfg.port}' + ''; + }; + }; + }; + + meta.maintainers = with maintainers; [ chickensoupandrice ]; +} + diff --git a/matrix.nix b/matrix.nix new file mode 100644 index 00000000..32330aac --- /dev/null +++ b/matrix.nix @@ -0,0 +1,258 @@ +{ config, lib, pkgs, ... }: + +{ + imports = [ ./matrix-slack.nix ./mautrix-whatsapp.nix ]; + security.acme.email = "anish+acme@lakhwara.com"; + security.acme.acceptTerms = true; + services.matrix-synapse = { + enable = true; + app_service_config_files = [ + # # The registration file is automatically generated after starting the appservice for the first time. + # # cp /var/lib/matrix-appservice-discord/discord-registration.yaml /var/lib/matrix-synapse/ + # # chown matrix-synapse:matrix-synapse /var/lib/matrix-synapse/discord-registration.yaml + # "/var/lib/matrix-synapse/discord-registration.yaml" + "/var/lib/matrix-synapse/telegram-registration.yaml" + "/var/lib/matrix-synapse/slack-registration.yaml" + "/var/lib/matrix-synapse/whatsapp-registration.yaml" + ]; + server_name = "sealight.xyz"; + registration_shared_secret = "hD9HQGTTDxp0mQsQ5JDsfudWMDiubmZENOgPchIvfBvUlPxlvQSvjoO4wn2L1seU"; + enable_metrics = true; + enable_registration = true; + database_type = "psycopg2"; + database_args = { + database = "matrix-synapse"; + }; + listeners = [ + { + port = 8448; + tls = false; + resources = [ + { + compress = true; + names = ["client" "federation"]; + } + ]; + } + ]; + extraConfig = '' + max_upload_size: "50M" + ''; + }; + + services.mautrix-telegram = { + enable = true; + environmentFile = /etc/secrets/telegram.env; # file containing the appservice and telegram tokens + # The appservice is pre-configured to use SQLite by default. It's also possible to use PostgreSQL. + settings = { + homeserver = { + address = "https://chat.sealight.xyz"; + domain = "sealight.xyz"; + }; + appservice = { + provisioning.enabled = false; + id = "telegram"; + public = { + enabled = false; + prefix = "/public"; + external = "https://chat.sealight.xyz/public"; + }; + # The service uses SQLite by default, but it's also possible to use PostgreSQL instead: + #database = "postgresql:///mautrix-telegram?host=/run/postgresql"; + }; + bridge = { + relaybot.authless_portals = false; + permissions = { + "@aynish:sealight.xyz" = "admin"; + }; + }; + }; + }; + + services.mautrix-whatsapp = { + enable = true; + # The appservice is pre-configured to use SQLite by default. It's also possible to use PostgreSQL. + configOptions = { + homeserver = { + address = "https://chat.sealight.xyz"; + domain = "sealight.xyz"; + }; + appservice = { + id = "whatsapp"; + address = http://localhost:9897; + hostname = "0.0.0.0"; + port = 9897; + database = { + type = "sqlite3"; + uri = "/var/lib/mautrix-whatsapp/mautrix-whatsapp.db"; + }; + state_store_path = "/var/lib/mautrix-whatsapp/mx-state.json"; + bot = { + username = "whatsappbot"; + displayname = "WhatsApp bridge bot"; + avatar = "mxc://maunium.net/NeXNQarUbrlYBiPCpprYsRqr"; + }; + as_token = ""; + hs_token = ""; + }; + bridge = { + username_template = "whatsapp_{{.}}"; + displayname_template = "{{if .Notify}}{{.Notify}}{{else}}{{.Jid}}{{end}} (WA)"; + command_prefix = "!wa"; + permissions = { + "@aynish:sealight.xyz" = 100; + }; + }; + logging = { + directory = "/var/lib/mautrix-whatsapp/logs"; + file_name_format = "{{.Date}}-{{.Index}}.log"; + file_date_format = "\"2006-01-02\""; + file_mode = 384; + timestamp_format = "Jan _2, 2006 15:04:05"; + print_level = "debug"; + }; + metrics = { + enabled = true; + listen = "http://localhost:5070"; + }; + }; + }; + + services.matrix-appservice-slack = { + enable = true; + #environmentFile = /etc/secrets/matrix-appservice-slack.env; + settings = { + homeserver = { + server_name = "sealight.xyz"; + url = "https://chat.sealight.xyz"; + }; + db = { + engine = "postgres"; + connectionString = "postgres://slackbridge_user:somethingverysecret@localhost/slack_bridge?sslmode=require"; + }; + matrix_admin_room = "!ikEWOHCcmoZtVzSmPF:sealight.xyz"; + username_prefix = "slack_"; + rtm = { + enable = true; + logging = "silent"; + }; + slack_hook_port = 9899; + enable_metrics = true; + puppeting = { + enabled = true; + onboard_users = true; + }; + oauth2 = { + client_id = "2832868673.1609454840563"; + client_secret = "079a7ca42b192e66a9ff32740a69cb9d"; + redirect_prefix = "https://sealight.xyz/slackbridge/"; + }; + #team_sync = { + # T02QGRJKT = { + # users = { + # enabled = false; + # }; + # channels = { + # enabled = true; + # alias_prefix = "qf_"; + # }; + # }; + #}; + }; + }; + + services.postgresql = { + enable = true; + ## postgresql user and db name remains in the + ## service.matrix-synapse.database_args setting which + ## by default is matrix-synapse + initialScript = pkgs.writeText "synapse-init.sql" '' + CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD "Da0?H*9i{x?,]|'kq@iBwlIzu"; + CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse" + TEMPLATE template0 + LC_COLLATE = "C" + LC_CTYPE = "C"; + ''; + }; + + networking.firewall = { + enable = true; + allowedTCPPorts = [ + 22 # SSH + 8448 # Matrix federation + 8008 + 80 + 443 + 9899 # Matrix <-> Slack Bridge Puppeting + ]; + }; + + nixpkgs.overlays = [ + (self: super: { + element-web = super.element-web.override { + conf = { + default_server_config = { + "m.homeserver" = { + "base_url" = "https://chat.sealight.xyz"; + "server_name" = "sealight.xyz"; + }; + "m.identity_server" = { + "base_url" = "https://vector.im"; + }; + }; + + ## jitsi will be setup later, + ## but we need to add to Riot configuration + #jitsi.preferredDomain = "jitsi.dangerousdemos.net"; + }; + }; + }) + ]; + + services.nginx = { + enable = true; + virtualHosts = { + + "sealight.xyz" = { + forceSSL = true; + enableACME = true; + locations."/" = { + root = "/var/www/sealight.xyz"; + }; + locations."/_matrix" = { + proxyPass = "http://localhost:8448"; + }; + locations."/slackbridge" = { + proxyPass = "http://localhost:9899"; + }; + }; + ## virtual host for Synapse + "chat.sealight.xyz" = { + forceSSL = true; + enableACME = true; + #useACMEHost = "sealight.xyz"; + locations."/" = { + proxyPass = "http://localhost:8448"; + }; + }; + + ## virtual host for Riot/Web + "element.sealight.xyz" = { + forceSSL = true; + enableACME = true; + #useACMEHost = "sealight.xyz"; + + ## root points to the riot-web package content, also configured via Nix + locations."/" = { + root = pkgs.element-web; + }; + }; + }; + ## other nginx specific best practices + recommendedGzipSettings = true; + recommendedOptimisation = true; + recommendedTlsSettings = true; + }; + + environment.systemPackages = with pkgs; [ matrix-appservice-slack ]; +} diff --git a/mautrix-whatsapp.nix b/mautrix-whatsapp.nix new file mode 100644 index 00000000..beaaa6f0 --- /dev/null +++ b/mautrix-whatsapp.nix @@ -0,0 +1,96 @@ +{ lib, config, pkgs, ... }: + +with lib; + +let + cfg = config.services.mautrix-whatsapp; + + configFile = pkgs.runCommand "mautrix-whatsapp" { + buildInputs = [ pkgs.mautrix-whatsapp pkgs.remarshal ]; + preferLocalBuild = true; + } '' + mkdir -p $out + ${pkgs.remarshal}/bin/json2yaml -i ${pkgs.writeText "config.json" (builtins.toJSON cfg.configOptions)} \ + -o $out/config.yaml + + ${pkgs.mautrix-whatsapp}/bin/mautrix-whatsapp -c $out/config.yaml -g -r $out/registration.yaml + ''; + +in +{ + options.services.mautrix-whatsapp = { + enable = mkEnableOption "Mautrix-whatsapp, a puppeting bridge between Matrix and WhatsApp."; + + configOptions = mkOption { + type = types.attrs; + description = '' + This options will be transform in YAML configuration file for the bridge + + Look here for documentation. + ''; + example = { + configOptions = { + homeserver = { + address = https://matrix.org; + domain = "matrix.org"; + }; + appservice = { + address = http://localhost:8080; + hostname = "0.0.0.0"; + port = 8080; + database = { + type = "sqlite3"; + uri = "/var/lib/mautrix-whatsapp/mautrix-whatsapp.db"; + }; + state_store_path = "/var/lib/mautrix-whatsapp/mx-state.json"; + id = "whatsapp"; + bot = { + username = "whatsappbot"; + displayname = "WhatsApp bridge bot"; + avatar = "mxc://maunium.net/NeXNQarUbrlYBiPCpprYsRqr"; + }; + as_token = ""; + hs_token = ""; + }; + bridge = { + username_template = "whatsapp_{{.}}"; + displayname_template = "{{if .Notify}}{{.Notify}}{{else}}{{.Jid}}{{end}} (WA)"; + command_prefix = "!wa"; + permissions = { + "@example:matrix.org" = 100; + }; + }; + logging = { + directory = "/var/lib/mautrix-whatsapp/logs"; + file_name_format = "{{.Date}}-{{.Index}}.log"; + file_date_format = "\"2006-01-02\""; + file_mode = 384; + timestamp_format = "Jan _2, 2006 15:04:05"; + print_level = "debug"; + }; + }; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.mautrix-whatsapp = { + description = "Mautrix-WhatsApp Service - A WhatsApp bridge for Matrix"; + after = [ "network.target" "matrix-synapse.service" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + DynamicUser = true; + StateDirectory = "mautrix-whatsapp"; + LoggingDir = "mautrix-whatsapp"; + ExecStart = '' + ${pkgs.mautrix-whatsapp}/bin/mautrix-whatsapp -c "${configFile}/config.yaml" + ''; + Restart = "on-failure"; + }; + }; + + #services.matrix-synapse.app_service_config_files = [ "${configFile}/registration.yaml" ]; + + }; +} diff --git a/monitoring.nix b/monitoring.nix new file mode 100644 index 00000000..b7b0c6ca --- /dev/null +++ b/monitoring.nix @@ -0,0 +1,17 @@ +{ config, pkgs, ... }: { + # grafana configuration + services.grafana = { + enable = true; + domain = "grafana.sealight.xyz"; + port = 2342; + addr = "127.0.0.1"; + }; + + # nginx reverse proxy + services.nginx.virtualHosts.${config.services.grafana.domain} = { + locations."/" = { + proxyPass = "http://127.0.0.1:${toString config.services.grafana.port}"; + proxyWebsockets = true; + }; + }; +} diff --git a/sealight.nix b/sealight.nix new file mode 100644 index 00000000..ed882042 --- /dev/null +++ b/sealight.nix @@ -0,0 +1,14 @@ +{ + services.nginx = { + enable = true; + vhost = { + "sealight.xyz" = { + forceSSL = true; + enableACME = true; + locations."/" = { + root = "/var/www/sealight.xyz" + }; + }; + }; + }; +} diff --git a/wireguard.nix b/wireguard.nix new file mode 100644 index 00000000..d4c3a84a --- /dev/null +++ b/wireguard.nix @@ -0,0 +1,27 @@ +{ config, pkgs, lib, ... }: +{ + environment.systemPackages = [ pkgs.wireguard pkgs.mosh ]; + networking.nat = { + enable = true; + externalInterface = "ens3"; + internalInterfaces = [ "wg0" ]; + }; + networking.firewall = { + allowedUDPPorts = [ 60990 ]; + }; + + networking.wireguard.interfaces.wg0 = { + ips = [ "10.0.69.1/24" ]; + listenPort = 60990; + privateKeyFile = "/var/lib/wireguard/priv"; + peers = [{ # bowl + publicKey = "EzvDUmHXo95S2jKRv/LkH5ItUNOBgM7aQQsaq1WfCRw="; + allowedIPs = [ "10.0.69.2/32" ]; + } + { # line + publicKey = "ViRabGV2Mv0SZUVWnoAUhIzaHEvqRXAZJLq3Bp1oN1k="; + allowedIPs = [ "10.0.69.3/32" ]; + } +]; + }; +}