From 478198d88ea6784162e7cd7a4a4105e29b8ac7be Mon Sep 17 00:00:00 2001 From: Alejandro Angulo Date: Mon, 5 Feb 2024 20:20:02 -0800 Subject: [PATCH] Added teslamate configuration --- modules/nixos/services/grafana/default.nix | 50 ++++++ modules/nixos/services/mosquitto/default.nix | 6 + modules/nixos/services/teslamate/default.nix | 165 ++++++++++++++++++ .../teslamate-grafana-dashboards/default.nix | 4 +- secrets/cf_dns_kilonull.age | Bin 746 -> 766 bytes secrets/hass_mqtt.age | Bin 520 -> 595 bytes secrets/nextcloud_admin.age | Bin 468 -> 487 bytes secrets/secrets.nix | 3 + secrets/teslamate_db.age | 8 + secrets/teslamate_encryption.age | 7 + secrets/teslamate_mqtt.age | Bin 0 -> 599 bytes secrets/theengs_ble_mqtt.age | Bin 412 -> 399 bytes systems/x86_64-linux/node/default.nix | 13 +- 13 files changed, 253 insertions(+), 3 deletions(-) create mode 100644 modules/nixos/services/teslamate/default.nix create mode 100644 secrets/teslamate_db.age create mode 100644 secrets/teslamate_encryption.age create mode 100644 secrets/teslamate_mqtt.age 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 2b90162ef49bf4ac6bf47f69cd239ba4e61651e6..d2e8a416a09bb3f80c14fe0aec490bcde844ba75 100644 GIT binary patch delta 698 zcmWmAOKZ~r003Yo>|lm4#Q9(ci+GrJB~8+%jk@BxG)=lDZIiwdHzaAZv`HUn(soHF z4u%IOJ|=?bb`lj4$A+GE5E&EHi5KVC=D~w71Vx0251b(4_Y=O)?SI>+ddEjW+uw*; zJXIJVE0mZkacnCyVn$OnMJa2NKn7*9U@LKyRe)MCI8e0JMln$pMA4{*J(d;AwLm>( z(u8TWnxIGW<(grT^#HDb!!!#nIS+fi5a!@`hGPMvt?$!Ako3zeb-fs6{E2TT&V zYg_v>Fsp(%9-@S%FBz**0+UT5DU<up z>w;5PC{x!fVMZ(3DDOAKNl$g1rur!$HG z6N(InfUdjGKhB(Md$rL6vw{0lKMr>&fuK1_Nk+DZg#tE-Q?qwm(O2) zQeAj|=-JqXty_PD)#)qq$=}~5mWLP1FP6RxZ6;SbULUx*^O84p6x!A|4?Y}G7JmWm zzDGCS_HnB{lY95=Iez5(jxlb&+rjsq8Z2$xPF>tO*?kWjie%EgUmq{r`9QwY-pu@5 VU)i-=Tko29x-_NuA55>!{sTk5|CIm$ delta 677 zcmWmAOK8&o0DxgO9b&&tpi^FX`1+YXvGASbWJj36b*o`EMs^3r645P{SFr` z;2PRW)cv^Njr%ONqgK<*+hob<@qn;3!ABC2xV0jQNIj1D1q3jhl&V8P2~H9w8-PfA zsG@5GXcWbGR8#qQ(qfX?A_3D9R^kN$0u0T-frvZ9G^7;GIXM(n_dNtRYihD8e$El zTy;E|X-Xl}r;7?;+H<^6EUK4IcI<8gH01}qYM3k*>Si{O#XUkfi*T6|A8eL@Es`iZ zVsr*;%4v&`3PFJuqQeDjE6M@|WmAa)>ua%m4GyLa2g!QO910qC_5UT#c6gMqtwF(CcK)V{Na>)wU%%rezl6TPbSO6wy5~9#>Ty?>8#bUGB ztU1OjA#kEzRA^3jH1v@wo6abDtf{(@dJe*(B#xN{mKK$`Wu-HAj#;?>b-w(wZ}A#- z?>KYn^79AtJ?rC-A0Ano!2^G~r#A+lTv-^Ig10s9wx>sXmsaN&2VI@}KfgV&a^uC& z+O7Hgj~)GU<@Dx-47KE1awi;9bCbDa!(Yb^S#<&j5>3*)a-kc_q{){UU>9w=ck#?{twX8iIwr!xxKBM&t@mQ{{XAf B`YZqd diff --git a/secrets/hass_mqtt.age b/secrets/hass_mqtt.age index 77772888d8adda98f6629baf5df8a5a46b053eb1..cbad0ea7f951fd3e3493222d9506b16b138bd331 100644 GIT binary patch delta 544 zcmWm9J&%)M003ZzTaL}a!I&5l6Jyfjpaoi3R9YwpEu~Q27Mfh{`2Oq{P$(U295lu_ zc$*g&C*$C%vxCX~fJ>YlP1MB2;U*o7&p&vc?OpD@JlM@Qp5iG|TgIjB)bh#EfKMVC zD;12L$~2%TA(ybZQf{LdTBM^PW<`CMQ**6$WEhRHiZ)SKL_1{6`U@4Ko48Y2sz^b| zeK^Yk#B~}ZS*KwmZn=RE%V}(n2!YkW%nN&wMRpTKZ}He7vxG$!TRL7DtU}fbtpbn? zRHn)B^A5>118mmioLcCivz2KJ6e>C0Q19bgW}4EXboTqkjwsx<4b+)F8FkoDQi>@c zy5PvpRK3#Ysy|#VHxeL;_NoKLmOCR*Ssb9P$)d7_!6sh2-A2|Pu4&ZQ_^c|X0zsAi zO87syd+*rHEr9r`Yi2I2;|?zM9>92voSrTu%~P__ZfWYwFFayz35wcDP@ z34@h&ti1)Lxh-nCf{B*eM?+=;oR-Y-axdoxV(f3+z)<>7DW~H}#jKKW&fwXiJ}f<% z=M@M+5SR$eh}Ei^k!(0DrihR!TEQe;8M;xAI&$VD6fkpL8c93{{e1Ot|KR(z^Y^Lr z^h)}C>mmFu_;d2>@gx1ilj8W(<=|}p?Zvkre~qut#c}V;i`)5~>)znzg>m)z&HkZ@ GpZ^2D<-0oo delta 469 zcmW;HJ&%)M003Z3I^n42Ok#4HR04&9V4@c&UtWQhk3vBcuTY>Zh4zI~S{%CSrtuON zC!O4G)x_OZFRquk>Egxl5{)K4|KK@oJZ(I`y-T`+Nli6A=kronU~1yANflh#z3;V~sb4`o3}TxglvUB}M= zu@FRDvE@Ec2I9K%vw-UaY+(iqnYS``EH3F6zxH-56f@PiozB5%R(ejw608$Y6Od4= zB}-JmvMn(5H^I8;K_1nthrBr-+0{zN@Y3qx?MQ2m#-NACU+NR)^yBZNAJ4uaf4++sN7u5SkKet5 s{I6Fo&SsxKeP~pd7Uu`&FCUfE&DYn@c&z+;b??NyMZRrZeslc(A6r_ZssI20 diff --git a/secrets/nextcloud_admin.age b/secrets/nextcloud_admin.age index c4180505c1a4c0c4e1f7e3bc4fe02532c3a6c045..bd629657e96b21581508a5d9a5a1b2e295557d23 100644 GIT binary patch delta 453 zcmWm7J&%)M007|O5(myz6LW)HoRlLLD1{E5@>M=c%R!H}=f?o#<(s}hfeJC{ z)R<^YbTLMMfRoYL#L>Y-6PKG@TpWzgA9%j*{MvbVu-lgsn2`j})ajQaV_foaiLp|4hb%5;$vs!sZ%SqSx2hDg+a(NCYfc#)Gj# z&2JikF|ZlGwbyN<@TNYC&el1(BUM;$f zMK;|uY}E`*>`;cT3`St}$6YZvx ziTlki4(<*P4kj)-b#OBh6a51YZpP;iJio9#_WI#&tD@3JGf$IpZAK#AM=Y|cZE}vN z#irZ>t58623Pdw=@knnc@tE?f^J7R-z7VUjAtZ*wm>i6$5Urs3wD`EmbM}KeKJbeG>!H4JA{pj=l 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 0000000000000000000000000000000000000000..72c90871279d42befbb97d4b5072e54fc5349748 GIT binary patch literal 599 zcmZ9{J&%)M003Z($;F_F(KtCVIG7MTpg>{J2(-r)+H0YFl%6r6g~I!mmhyQV>UME5 z#>vU(c8SKpS%+JUad9v)@#0Nx4h{~wY1CW&1y3F~@Ns`RO?Ov0h?iI6G*~Z51iy}E zS%~`{#V{m~=FF%g3#e*ii%F(HAh`2U?6YN2Y)oMYB(jV<#@LHjhy{LL@qqSQBqO zk;B|3jW9rP97pFt%>BEMAzcg_BA7};wil73g;0)cE;B2m^37n2>xcw_9PT$R@4tpW z6#C2BiK9;sZd^Ni@cVFW@637i&is_J{TaSD;5X*d!LPk1>QCZTEae~nyqurD{OtAz p{pQj4VePc|OD{R>5}&Pf0O literal 0 HcmV?d00001 diff --git a/secrets/theengs_ble_mqtt.age b/secrets/theengs_ble_mqtt.age index 0f59fc6fa4ab33f7d8e73c11d34376ba64ff20d3..a350faf5c42de23c031f031be1b281855bf420dd 100644 GIT binary patch delta 364 zcmV-y0h9im1CIlcEPr)vZ7VoTd2=ghNJ~(4V`*16R#|vaGirJ=R5LF@WLHyEZDngU zGEhrrNeV?)aWhs>az<)VD{gZ`P)I{(L}PAIX?9XLOlEmkMQCPtZgXyJZdN%kX9_Jo zAaH4REpRe5HXv0=MMrvPAVD^EP)}rOZ*Xr$FJU$}Sx+{4LVtQ;W@J@VM|Mp)V`Vj2 zcXo9)Sy@<8M>h&bOn5UZK|*6ha%xq2MoDOPXlO}UNOv(zcUCf5Yj$=)YISQ)QEoz0 zbU_L&J|H7gFD++sWnpt=3OHqBN>5@!cQ0y7GH6L^QgCfeZ)HYlOK5pxSx04K3N0-y zAUADpHdc6aS7j?nLwIgYXlgb@P%v3qac)#NMN3XeG<7g|VOm8^GkRe{RSMl6!d@5q zaSMI88EaOGrjkY;AcsQ+jl2WSba`e=LTh1gYI$xmM>JJ8L`+abbud_BH&iflO$seO zAaH4REpRe5HXv0=MMrvPAVD;4LO5$`LTxZMOEPX<gAtPJdK-LRw>HY;$v2NqIpu zW=m&ybWUwd86E$Ovay3p(U4QW!bhq`^&tR06jkqgsL! X+8dA(H7-={Ys=r~=={r*s6{#r2u6Wv 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";