diff --git a/modules/nixos/services/grafana/default.nix b/modules/nixos/services/grafana/default.nix index 49035fe..1c74db3 100644 --- a/modules/nixos/services/grafana/default.nix +++ b/modules/nixos/services/grafana/default.nix @@ -21,6 +21,13 @@ in { }; }; config = mkIf cfg.enable { + age.secrets = { + teslamate_db_for_grafana = { + file = ../../../../secrets/teslamate_db.age; + owner = "grafana"; + }; + }; + services.grafana = { enable = true; settings.server = { @@ -47,6 +54,49 @@ in { access = "proxy"; url = "http://127.0.0.1:${toString config.services.loki.configuration.server.http_listen_port}"; } + { + name = "TeslaMate"; + type = "postgres"; + access = "proxy"; + url = "localhost:5432"; + user = "teslamate"; + database = "teslamate"; + secureJsonData = { + password = "$__file{${config.age.secrets.teslamate_db_for_grafana.path}}"; + }; + jsonData = { + # TODO: Automate this somehow? + postgresVersion = "1400"; + sslmode = "disable"; + }; + } + ]; + }; + + dashboards = { + settings.providers = [ + { + name = "teslamate"; + orgId = 1; + folder = "TeslaMate"; + folderUid = "Nr4ofiDZk"; + type = "file"; + disableDeletion = false; + editable = true; + updateIntervalSeconds = 86400; + options.path = "${pkgs.aa.teslamate-grafana-dashboards}/dashboards"; + } + { + name = "teslamate_internal"; + orgId = 1; + folder = "TeslaMate/Internal"; + folderUid = "Nr5ofiDZk"; + type = "file"; + disableDeletion = false; + editable = true; + updateIntervalSeconds = 86400; + options.path = "${pkgs.aa.teslamate-grafana-dashboards}/dashboards/internal"; + } ]; }; }; diff --git a/modules/nixos/services/mosquitto/default.nix b/modules/nixos/services/mosquitto/default.nix index 7dd45a3..dfc16b9 100644 --- a/modules/nixos/services/mosquitto/default.nix +++ b/modules/nixos/services/mosquitto/default.nix @@ -17,6 +17,7 @@ in { age.secrets = { hass_mqtt.file = ../../../../secrets/hass_mqtt.age; theengs_ble_mqtt.file = ../../../../secrets/theengs_ble_mqtt.age; + teslamate_mqtt.file = ../../../../secrets/teslamate_mqtt.age; }; services.mosquitto = { @@ -28,6 +29,7 @@ in { acl = [ "readwrite home/#" "readwrite homeassistant/#" + "read teslamate/#" ]; passwordFile = config.age.secrets.hass_mqtt.path; }; @@ -38,6 +40,10 @@ in { ]; passwordFile = config.age.secrets.theengs_ble_mqtt.path; }; + teslamate = { + acl = ["readwrite teslamate/#"]; + passwordFile = config.age.secrets.teslamate_mqtt.path; + }; }; } ]; diff --git a/modules/nixos/services/teslamate/default.nix b/modules/nixos/services/teslamate/default.nix new file mode 100644 index 0000000..6d464a6 --- /dev/null +++ b/modules/nixos/services/teslamate/default.nix @@ -0,0 +1,165 @@ +{ + options, + config, + lib, + pkgs, + ... +}: +with lib; let + cfg = config.aa.services.teslamate; +in { + options.aa.services.teslamate = with types; { + enable = mkEnableOption "teslamate"; + + acmeCertName = mkOption { + type = str; + default = ""; + description = '' + If set to a non-empty string, forces SSL with the supplied acme + certificate. + ''; + }; + + user = mkOption { + type = str; + default = "teslamate"; + description = '' + The user that should run teslamate + ''; + }; + + group = mkOption { + type = str; + default = "teslamate"; + description = '' + The group that should be assigned to the user running teslamate + ''; + }; + + database = { + host = mkOption { + type = str; + default = "127.0.0.1"; + description = '' + Database host address + ''; + }; + + name = mkOption { + type = str; + default = "teslamate"; + description = '' + The database name + ''; + }; + + user = mkOption { + type = str; + default = "teslamate"; + description = '' + The user that should have access to the database + ''; + }; + + passwordFile = mkOption { + type = path; + description = lib.mdDoc '' + A file containing the password corresponding to + {option}`database.user` + ''; + }; + + createDatabase = mkOption { + type = bool; + default = false; + description = '' + Whether to create a local database automatically. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + age.secrets = { + teslamate_encryption.file = ../../../../secrets/teslamate_encryption.age; + teslamate_mqtt.file = ../../../../secrets/teslamate_mqtt.age; + }; + + # docker-teslamate is the name of the service generated by the + # `virtualisation.oci-contianers` block below + systemd.services."docker-teslamate" = { + preStart = '' + mkdir -p /var/lib/teslamate + + # Create file if it doesn't exist, truncate it if does + touch /var/lib/teslamate/env + echo "" > /var/lib/teslamate/env + + chmod 600 /var/lib/teslamate/env + + echo ENCRYPTION_KEY="$(cat ${config.age.secrets.teslamate_encryption.path})" >> /var/lib/teslamate/env + echo DATABASE_PASS="$(cat ${cfg.database.passwordFile})" >> /var/lib/teslamate/env + echo MQTT_PASSWORD="$(cat ${config.age.secrets.teslamate_mqtt.path})" >> /var/lib/teslamate/env + ''; + }; + + virtualisation.oci-containers = { + backend = "docker"; + containers."teslamate" = { + image = "ghcr.io/teslamate-org/teslamate:latest"; + environmentFiles = ["/var/lib/teslamate/env"]; + environment = { + # TODO: Make this configurable + PORT = "4000"; + DATABASE_USER = cfg.database.user; + DATABASE_NAME = cfg.database.name; + DATABASE_HOST = cfg.database.host; + # TODO: Make this configurable. + MQTT_HOST = "192.168.113.42"; + MQTT_USERNAME = "teslamate"; + TZ = "America/Los_Angeles"; + }; + extraOptions = ["--cap-drop=all" "--network=host"]; + # TODO: Make this configurable + ports = ["4000:4000"]; + }; + }; + + users.users.${cfg.user} = { + isSystemUser = true; + group = cfg.group; + }; + users.groups.${cfg.group} = {}; + + services.postgresql = optionalAttrs cfg.database.createDatabase { + enable = mkDefault true; + + ensureDatabases = [cfg.database.name]; + ensureUsers = [ + { + name = cfg.database.user; + ensureDBOwnership = true; + } + ]; + }; + + services.nginx = { + enable = true; + virtualHosts."teslamate.kilonull.com" = + { + locations."/" = { + recommendedProxySettings = true; + proxyWebsockets = true; + # TODO: Make port configurable. + proxyPass = "http://127.0.0.1:4000"; + }; + } + // lib.optionalAttrs (cfg.acmeCertName != "") { + forceSSL = true; + useACMEHost = cfg.acmeCertName; + }; + }; + + networking.firewall.allowedTCPPorts = [4000]; + }; +} diff --git a/packages/teslamate-grafana-dashboards/default.nix b/packages/teslamate-grafana-dashboards/default.nix index 2bae7dc..a1fe0c9 100644 --- a/packages/teslamate-grafana-dashboards/default.nix +++ b/packages/teslamate-grafana-dashboards/default.nix @@ -6,13 +6,13 @@ }: stdenv.mkDerivation rec { pname = "teslamate-grafana-dashboards"; - version = "1.28.2"; + version = "1.28.3"; src = fetchFromGitHub { owner = "teslamate-org"; repo = "teslamate"; rev = "v${version}"; - hash = "sha256-CH3u6ijzvVdjfTVu06UcyW4NhVQKeUKtC/j+UeDELNc="; + hash = "sha256-Iky9zWb3m/ex/amZw2dP5ZOpFw3uyg0JG6e9PkV+t4A="; }; dontBuild = true; diff --git a/secrets/cf_dns_kilonull.age b/secrets/cf_dns_kilonull.age index 2b90162..d2e8a41 100644 Binary files a/secrets/cf_dns_kilonull.age and b/secrets/cf_dns_kilonull.age differ diff --git a/secrets/hass_mqtt.age b/secrets/hass_mqtt.age index 7777288..cbad0ea 100644 Binary files a/secrets/hass_mqtt.age and b/secrets/hass_mqtt.age differ diff --git a/secrets/nextcloud_admin.age b/secrets/nextcloud_admin.age index c418050..bd62965 100644 Binary files a/secrets/nextcloud_admin.age and b/secrets/nextcloud_admin.age differ diff --git a/secrets/secrets.nix b/secrets/secrets.nix index 70bbea6..70c26d5 100644 --- a/secrets/secrets.nix +++ b/secrets/secrets.nix @@ -12,4 +12,7 @@ in { "nextcloud_admin.age".publicKeys = [users.me machines.node]; "theengs_ble_mqtt.age".publicKeys = [users.me machines.pi4]; "hass_mqtt.age".publicKeys = [users.me machines.pi4 machines.node]; + "teslamate_db.age".publicKeys = [users.me machines.node]; + "teslamate_mqtt.age".publicKeys = [users.me machines.pi4 machines.node]; + "teslamate_encryption.age".publicKeys = [users.me machines.node]; } diff --git a/secrets/teslamate_db.age b/secrets/teslamate_db.age new file mode 100644 index 0000000..a407e7c --- /dev/null +++ b/secrets/teslamate_db.age @@ -0,0 +1,8 @@ +age-encryption.org/v1 +-> piv-p256 UIEGzg A2wMmiESse22Vlfg8lbSUz3t2GwGvTE49WPKV81egU0Z +I/7v83H42x/2hhOMRAbyUNgCY/GugzSHr9y4EFWZ3rc +-> ssh-ed25519 Yk7ehg CVe6AyDEb9Ulg5Zn5GQAoQT8jXBkjAWx6CP847XU118 +HIaJ4+oos94I7m1UEEErP2jhYNhAmG8/RRmNZrWSxcE +--- U3FNQyx/Ncl59978k3kMtYKNZayk13gAubt+l5RqFJw +W½ÊSÖ4ÿÐB mHYªÖ܇nú`"ß|´¢wèH¦À’ +Wˆ4› \ No newline at end of file diff --git a/secrets/teslamate_encryption.age b/secrets/teslamate_encryption.age new file mode 100644 index 0000000..0473f4b --- /dev/null +++ b/secrets/teslamate_encryption.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> piv-p256 UIEGzg AuDH1UtCo2ABWz85Uq+kS9CA/X7ZMk4PwCbtSSMHU1rL +pNyYJm4Boy64z0+T9xW/Emgm3cT3oLXXpqtkSGFuWsE +-> ssh-ed25519 Yk7ehg MlNtvtKwytkGrlcF2kBdYA/X4EaCVxuDCPRLkn9qWgo +cC0+E8s0VAKSBojprhU23IP3q77x/VRS7tbfOpPAa10 +--- c0LU0eTiBBo1puXxtpfWQM4iBJODzYZ0dAzGSheQ6Og +ñïËì;¨ðvø;ä×UG•óËDÉ.au¬¸#¬¿†Iüê±ß'ÿ‡ÎI<øI¯‹´ý›viôºÕS9VnÎYþí \ No newline at end of file diff --git a/secrets/teslamate_mqtt.age b/secrets/teslamate_mqtt.age new file mode 100644 index 0000000..72c9087 Binary files /dev/null and b/secrets/teslamate_mqtt.age differ diff --git a/secrets/theengs_ble_mqtt.age b/secrets/theengs_ble_mqtt.age index 0f59fc6..a350faf 100644 Binary files a/secrets/theengs_ble_mqtt.age and b/secrets/theengs_ble_mqtt.age differ diff --git a/systems/x86_64-linux/node/default.nix b/systems/x86_64-linux/node/default.nix index c9acd12..47d9360 100644 --- a/systems/x86_64-linux/node/default.nix +++ b/systems/x86_64-linux/node/default.nix @@ -8,7 +8,10 @@ ./zfs.nix ]; - age.secrets.cf_dns_kilonull.file = ../../../secrets/cf_dns_kilonull.age; + age.secrets = { + cf_dns_kilonull.file = ../../../secrets/cf_dns_kilonull.age; + teslamate_db.file = ../../../secrets/teslamate_db.age; + }; aa = { nix.enable = true; @@ -47,6 +50,14 @@ remoteTargetDatasets = ["tank/backups"]; remoteTargetPublicKeys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAhA+9O2OBMDH1Xnj6isu36df5TOdZG8aEA4JpN2K60e syncoid@gospel"]; }; + services.teslamate = { + enable = true; + database = { + createDatabase = true; + passwordFile = config.age.secrets.teslamate_db.path; + }; + acmeCertName = "kilonull.com"; + }; services.gitea = { enable = true; acmeCertName = "kilonull.com";