Compare commits

..
Author SHA1 Message Date
Richie d08915b541 feat(kanidm): add Kanidm service configuration and documentation 2026-10-16 21:16:29 -04:00
12 changed files with 443 additions and 646 deletions

No files matched your search

@@ -2,7 +2,6 @@ _final: prev:
(import ./abseil { inherit prev; }) (import ./abseil { inherit prev; })
// { // {
gnutls = import ./gnutls { inherit (prev) gnutls; }; gnutls = import ./gnutls { inherit (prev) gnutls; };
libjpeg_turbo = import ./libjpeg-turbo { inherit (prev) lib libjpeg_turbo stdenv; };
prometheus = import ./prometheus { inherit (prev) prometheus; }; prometheus = import ./prometheus { inherit (prev) prometheus; };
zopfli = import ./zopfli { inherit (prev) zopfli; }; zopfli = import ./zopfli { inherit (prev) zopfli; };
@@ -1,15 +0,0 @@
{
lib,
libjpeg_turbo,
stdenv,
}:
libjpeg_turbo.overrideAttrs (old: {
# This overlay also reaches pkgsi686Linux. Its 12-bit floating-point code
# uses x87, while x86-64-v3 uses FMA. Keep the tests enabled for both.
cmakeFlags =
(old.cmakeFlags or [ ])
++ lib.optional (
stdenv.hostPlatform.isx86_64 && (stdenv.hostPlatform.gcc.arch or null) == "x86-64-v3"
) "-DFLOATTEST12=fp-contract"
++ lib.optional stdenv.hostPlatform.isi686 "-DFLOATTEST12=387";
})
Generated
+21 -21
View File
@@ -7,11 +7,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1792526520, "lastModified": 1789770686,
"narHash": "sha256-3hSJlv8mUVnZPMWK2+CM7a9O+nR6Z4mCh27NfLCmTPc=", "narHash": "sha256-uZkBR7yHdIKUFB5SZdfgh1qkGfI3XmYmI/lTiquxbck=",
"owner": "nix-community", "owner": "nix-community",
"repo": "disko", "repo": "disko",
"rev": "cedd626e11b635470fa6f9fdb8ad79b0075dd7f9", "rev": "725ea35e410ad83be4931d1bff7e090eacaf3563",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -45,11 +45,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1793193205, "lastModified": 1791293500,
"narHash": "sha256-X6xa6sGA7RnJQuZGbFMC+K+KToWAlBDCCjJXfTwEPsw=", "narHash": "sha256-3hwJd2/vG/VYV1P/g7oDgdbNsWODhN5F//Od+hPMm2k=",
"owner": "nix-community", "owner": "nix-community",
"repo": "home-manager", "repo": "home-manager",
"rev": "095fef2f265c3c0da4fe315efba8f8a04495ab34", "rev": "0e11b7bcba1d74ef8ea10bfdfb1e1a5811a21087",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -67,11 +67,11 @@
"rust-overlay": "rust-overlay" "rust-overlay": "rust-overlay"
}, },
"locked": { "locked": {
"lastModified": 1793017781, "lastModified": 1791127101,
"narHash": "sha256-YRem1WRn//ruQ7QukBCjpn/Uwmd17vGLml8w3i+Swd8=", "narHash": "sha256-SmrBBhcv9iNHA2vlafi7Zpe8bkcsMjVNSmCCVf5/jrQ=",
"owner": "LLukas22", "owner": "LLukas22",
"repo": "Jellyswarrm", "repo": "Jellyswarrm",
"rev": "90cca4fe7e6f855453866b71ca5b38773342a0c4", "rev": "0b4301c6c74efff9c3634296913032579c98faf6",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -85,11 +85,11 @@
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
}, },
"locked": { "locked": {
"lastModified": 1793236063, "lastModified": 1791128201,
"narHash": "sha256-jGFe4xdrCw6xpgH2zgKwslACyt/NGuPwGILPEMFxmcQ=", "narHash": "sha256-rlsYY/Dg/LhbIj51Aa6TLvRpZeb3KZgUTd7llQ5XcJE=",
"owner": "nixos", "owner": "nixos",
"repo": "nixos-hardware", "repo": "nixos-hardware",
"rev": "b6754c901c21478fd8450d62101e712b2a4f0d75", "rev": "31cc5f4d9b9ba601071e8b8504601b9b176e2756",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -114,11 +114,11 @@
}, },
"nixpkgs-master": { "nixpkgs-master": {
"locked": { "locked": {
"lastModified": 1793315900, "lastModified": 1791296764,
"narHash": "sha256-z5lZoCwheBzMgRtfYlGLMhmPwhv1Rty16Kf269n8ctc=", "narHash": "sha256-N2b6Uxdr1MFhscEPqDyntTTtGZASgq3XYE0ujRzfeNI=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "db379a58acfef9783b46ffde2957dac397c92b85", "rev": "a15afac8691bd24f746255ffbd76306693e4cf7a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -146,11 +146,11 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1793110635, "lastModified": 1791260994,
"narHash": "sha256-dhoW6VgFWlaodSY5jrBn+CF2FLmaUvDVgM7+C7T2+NM=", "narHash": "sha256-Miqqk/ammqnTUxaoCyvtwoPeLbI1ksFyLxi/CazZpWY=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "31ecbaca1bdefa45dffc409f0250bd8039ea0010", "rev": "151fa4e8ddfdd8dd25d945ad94ed54a13de9f6e4",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -201,11 +201,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1793052255, "lastModified": 1791103873,
"narHash": "sha256-Y/JIHTnMlOabDfyaSBubWGLDIVGihLEUuoMhFvQKaDo=", "narHash": "sha256-nFxM+pKoZ8LJAEnUXARyCaOAloWgaW9kZQOSjWzKcTE=",
"owner": "Mic92", "owner": "Mic92",
"repo": "sops-nix", "repo": "sops-nix",
"rev": "60182817ee048ac94b4c68ea76d5ef2e8aac730d", "rev": "dcd241ba97088c22569d1573286e1b9daad340c0",
"type": "github" "type": "github"
}, },
"original": { "original": {
-2
View File
@@ -5,13 +5,11 @@
extra-substituters = [ extra-substituters = [
"https://cache.nixos.org/?priority=2&want-mass-query=true" "https://cache.nixos.org/?priority=2&want-mass-query=true"
"https://nix-community.cachix.org/?priority=10&want-mass-query=true" "https://nix-community.cachix.org/?priority=10&want-mass-query=true"
"https://attic.nayeonie.com/nix-cache/?priority=10&want-mass-query=true"
]; ];
extra-trusted-public-keys = [ extra-trusted-public-keys = [
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" # cspell:disable-line "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" # cspell:disable-line
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" # cspell:disable-line "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" # cspell:disable-line
"cache-nix-dot:Od9KN34LXc6Lu7y1ozzV1kIXZa8coClozgth/SYE7dU=" # cspell:disable-line "cache-nix-dot:Od9KN34LXc6Lu7y1ozzV1kIXZa8coClozgth/SYE7dU=" # cspell:disable-line
"nix-cache:grGRsHhqNDhkEuTODvHJXYmoCClntC+U8XAJQzwMaZM=" # cspell:disable-line
]; ];
}; };
@@ -82,7 +82,6 @@
extraComponents = [ extraComponents = [
"isal" "isal"
"modbus" # for victron modbus integration "modbus" # for victron modbus integration
"template" # MultiPlus mode selector, input current limit and derived sensors
]; ];
customComponents = with pkgs.x86-v1.home-assistant-custom-components; [ customComponents = with pkgs.x86-v1.home-assistant-custom-components; [
garmin_connect garmin_connect
@@ -0,0 +1,122 @@
title: Van Overview
views:
- title: Overview
path: overview
type: sections
sections:
# Battery Status Section
- type: grid
title: Battery Status
cards:
- type: gauge
entity: sensor.jk0_soc
name: Battery 0
min: 0
max: 100
severity:
green: 50
yellow: 20
red: 0
needle: true
- type: gauge
entity: sensor.jk1_soc
name: Battery 1
min: 0
max: 100
severity:
green: 50
yellow: 20
red: 0
needle: true
- type: entity
entity: sensor.jk0_total_voltage
name: Batt 0 Voltage
- type: entity
entity: sensor.jk1_total_voltage
name: Batt 1 Voltage
# Solar Production Section
- type: grid
title: Solar Production
cards:
- type: gauge
entity: sensor.solar_wattage
name: Solar Now
min: 0
max: 400
severity:
green: 100
yellow: 50
red: 0
needle: true
- type: entity
entity: sensor.solar_yield_today
name: Today's Yield
- type: entity
entity: sensor.estimated_energy_production_today
name: Forecast Today
- type: entity
entity: sensor.estimated_energy_production_tomorrow
name: Forecast Tomorrow
# Environment Section
- type: grid
title: Van Environment
cards:
- type: entity
entity: sensor.environment_temperature
name: Inside Temp
- type: entity
entity: sensor.environment_humidity
name: Inside Humidity
- type: entity
entity: sensor.current_weather_temperature
name: Outside Temp
- type: entity
entity: sensor.current_weather_humidity
name: Outside Humidity
- type: entity
entity: sensor.current_weather_condition
name: Weather
# Charger Status Section
- type: grid
title: Charger
cards:
- type: entity
entity: sensor.charger_dc_wattage
name: Charging Power
- type: entity
entity: sensor.charger_charge_state
name: Charge State
- type: entity
entity: sensor.charger_dc_wattage_daily
name: Charged Today
# Location Section
- type: grid
title: Location
cards:
- type: entity
entity: sensor.gps_location
name: Current Location
- type: entity
entity: sensor.gps_speed
name: Speed
- type: entity
entity: binary_sensor.gps_fix_available
name: GPS Fix
@@ -220,120 +220,10 @@ modbus:
state_class: measurement state_class: measurement
unique_id: gps_altitude_id_1 unique_id: gps_altitude_id_1
# ---- MULTIPLUS-II (VE.Bus, Cerbo GX unit ID 227) ---- # ---- CHARGER (Unit ID 223) ----
# Confirmed on this GX: one AC phase, AC input 1, 30 A shore-power plug. - name: Charger Output 1 Voltage
# Register map: https://www.victronenergy.com/upload/documents/CCGX-Modbus-TCP-register-list-3.81.xlsx slave: 223
# Victron's scale column is a divisor: AC power registers use 0.1, address: 2307
# so Home Assistant must multiply their signed raw values by 10.
# Change the unit ID anchor here if the GX service mapping changes.
- name: MultiPlus AC Input Voltage
unique_id: multiplus_ac_input_voltage
slave: &multiplus_unit_id 227
address: 3
input_type: holding
data_type: uint16
scale: 0.1
precision: 1
unit_of_measurement: "V"
device_class: voltage
state_class: measurement
scan_interval: 10
- name: MultiPlus AC Input Current
unique_id: multiplus_ac_input_current
slave: *multiplus_unit_id
address: 6
input_type: holding
data_type: int16
scale: 0.1
precision: 1
unit_of_measurement: "A"
device_class: current
state_class: measurement
scan_interval: 10
- name: MultiPlus AC Input Frequency
unique_id: multiplus_ac_input_frequency
slave: *multiplus_unit_id
address: 9
input_type: holding
data_type: int16
scale: 0.01
precision: 2
unit_of_measurement: "Hz"
device_class: frequency
state_class: measurement
scan_interval: 10
- name: MultiPlus AC Input Power
unique_id: multiplus_ac_input_power
slave: *multiplus_unit_id
address: 12
input_type: holding
data_type: int16
scale: 10
precision: 0
unit_of_measurement: "W"
device_class: power
state_class: measurement
scan_interval: 10
- name: MultiPlus AC Output Voltage
unique_id: multiplus_ac_output_voltage
slave: *multiplus_unit_id
address: 15
input_type: holding
data_type: uint16
scale: 0.1
precision: 1
unit_of_measurement: "V"
device_class: voltage
state_class: measurement
scan_interval: 10
- name: MultiPlus AC Output Current
unique_id: multiplus_ac_output_current
slave: *multiplus_unit_id
address: 18
input_type: holding
data_type: int16
scale: 0.1
precision: 1
unit_of_measurement: "A"
device_class: current
state_class: measurement
scan_interval: 10
- name: MultiPlus AC Output Frequency
unique_id: multiplus_ac_output_frequency
slave: *multiplus_unit_id
address: 21
input_type: holding
data_type: int16
scale: 0.01
precision: 2
unit_of_measurement: "Hz"
device_class: frequency
state_class: measurement
scan_interval: 10
- name: MultiPlus AC Output Power
unique_id: multiplus_ac_output_power
slave: *multiplus_unit_id
address: 23
input_type: holding
data_type: int16
scale: 10
precision: 0
unit_of_measurement: "W"
device_class: power
state_class: measurement
scan_interval: 10
- name: MultiPlus Battery Voltage
unique_id: multiplus_battery_voltage
slave: *multiplus_unit_id
address: 26
input_type: holding input_type: holding
data_type: uint16 data_type: uint16
scale: 0.01 scale: 0.01
@@ -341,12 +231,11 @@ modbus:
unit_of_measurement: "V" unit_of_measurement: "V"
device_class: voltage device_class: voltage
state_class: measurement state_class: measurement
scan_interval: 10 unique_id: charger_output_1_voltage
- name: MultiPlus Battery Current - name: Charger Output 1 Current
unique_id: multiplus_battery_current slave: 223
slave: *multiplus_unit_id address: 2308
address: 27
input_type: holding input_type: holding
data_type: int16 data_type: int16
scale: 0.1 scale: 0.1
@@ -354,12 +243,11 @@ modbus:
unit_of_measurement: "A" unit_of_measurement: "A"
device_class: current device_class: current
state_class: measurement state_class: measurement
scan_interval: 10 unique_id: charger_output_1_current
- name: MultiPlus Battery Temperature - name: Charger Output 1 Temperature
unique_id: multiplus_battery_temperature slave: 223
slave: *multiplus_unit_id address: 2309
address: 61
input_type: holding input_type: holding
data_type: int16 data_type: int16
scale: 0.1 scale: 0.1
@@ -367,23 +255,11 @@ modbus:
unit_of_measurement: "°C" unit_of_measurement: "°C"
device_class: temperature device_class: temperature
state_class: measurement state_class: measurement
unique_id: charger_output_1_temperature
- name: MultiPlus State of Charge - name: Charger AC Current
unique_id: multiplus_state_of_charge slave: 223
slave: *multiplus_unit_id address: 2314
address: 30
input_type: holding
data_type: uint16
scale: 0.1
precision: 1
unit_of_measurement: "%"
device_class: battery
state_class: measurement
- name: MultiPlus AC Input Current Limit Readback
unique_id: multiplus_ac_input_current_limit_readback
slave: *multiplus_unit_id
address: 22
input_type: holding input_type: holding
data_type: int16 data_type: int16
scale: 0.1 scale: 0.1
@@ -391,272 +267,73 @@ modbus:
unit_of_measurement: "A" unit_of_measurement: "A"
device_class: current device_class: current
state_class: measurement state_class: measurement
scan_interval: 10 unique_id: charger_ac_current
- name: MultiPlus Mode Raw - name: Charger AC Current Limit
unique_id: multiplus_mode_raw slave: 223
slave: *multiplus_unit_id address: 2316
address: 33
input_type: holding input_type: holding
data_type: uint16 data_type: int16
scan_interval: 10 scale: 0.1
precision: 1
- name: MultiPlus State Raw unit_of_measurement: "A"
unique_id: multiplus_state_raw device_class: current
slave: *multiplus_unit_id
address: 31
input_type: holding
data_type: uint16
scan_interval: 10
- name: MultiPlus Charge State Raw
unique_id: multiplus_charge_state_raw
slave: *multiplus_unit_id
address: 95
input_type: holding
data_type: uint16
scan_interval: 10
- name: MultiPlus Active Input Raw
unique_id: multiplus_active_input_raw
slave: *multiplus_unit_id
address: 29
input_type: holding
data_type: uint16
scan_interval: 10
- name: MultiPlus Phase Count
unique_id: multiplus_phase_count
slave: *multiplus_unit_id
address: 28
input_type: holding
data_type: uint16
- name: MultiPlus VE Bus Error
unique_id: multiplus_ve_bus_error
slave: *multiplus_unit_id
address: 32
input_type: holding
data_type: uint16
- name: MultiPlus High Temperature Alarm Raw
unique_id: multiplus_high_temperature_alarm_raw
slave: *multiplus_unit_id
address: 34
input_type: holding
data_type: uint16
- name: MultiPlus Low Battery Alarm Raw
unique_id: multiplus_low_battery_alarm_raw
slave: *multiplus_unit_id
address: 35
input_type: holding
data_type: uint16
- name: MultiPlus Overload Alarm Raw
unique_id: multiplus_overload_alarm_raw
slave: *multiplus_unit_id
address: 36
input_type: holding
data_type: uint16
- name: MultiPlus Ripple Alarm Raw
unique_id: multiplus_ripple_alarm_raw
slave: *multiplus_unit_id
address: 47
input_type: holding
data_type: uint16
- name: MultiPlus Temperature Sensor Alarm Raw
unique_id: multiplus_temperature_sensor_alarm_raw
slave: *multiplus_unit_id
address: 42
input_type: holding
data_type: uint16
- name: MultiPlus Voltage Sensor Alarm Raw
unique_id: multiplus_voltage_sensor_alarm_raw
slave: *multiplus_unit_id
address: 43
input_type: holding
data_type: uint16
- name: MultiPlus Grid Lost Alarm Raw
unique_id: multiplus_grid_lost_alarm_raw
slave: *multiplus_unit_id
address: 64
input_type: holding
data_type: uint16
- name: MultiPlus BMS Pre Alarm Raw
unique_id: multiplus_bms_pre_alarm_raw
slave: *multiplus_unit_id
address: 94
input_type: holding
data_type: uint16
- name: MultiPlus BMS Allows Charging Raw
unique_id: multiplus_bms_allows_charging_raw
slave: *multiplus_unit_id
address: 57
input_type: holding
data_type: uint16
- name: MultiPlus BMS Allows Discharging Raw
unique_id: multiplus_bms_allows_discharging_raw
slave: *multiplus_unit_id
address: 58
input_type: holding
data_type: uint16
- name: MultiPlus BMS Expected Raw
unique_id: multiplus_bms_expected_raw
slave: *multiplus_unit_id
address: 59
input_type: holding
data_type: uint16
- name: MultiPlus BMS Error
unique_id: multiplus_bms_error
slave: *multiplus_unit_id
address: 60
input_type: holding
data_type: uint16
- name: MultiPlus AC Input Ignored Raw
unique_id: multiplus_ac_input_ignored_raw
slave: *multiplus_unit_id
address: 69
input_type: holding
data_type: uint16
- name: MultiPlus AC Input to Output Energy
unique_id: multiplus_ac_input_to_output_energy
slave: *multiplus_unit_id
address: 74
input_type: holding
data_type: uint32
scale: 0.01
precision: 2
unit_of_measurement: "kWh"
device_class: energy
state_class: total_increasing
# Hardware counters reset on Multi/GX restart; total_increasing handles this.
- name: MultiPlus AC Input to Battery Energy
unique_id: multiplus_ac_input_to_battery_energy
slave: *multiplus_unit_id
address: 76
input_type: holding
data_type: uint32
scale: 0.01
precision: 2
unit_of_measurement: "kWh"
device_class: energy
state_class: total_increasing
- name: MultiPlus AC Output to Input Energy
unique_id: multiplus_ac_output_to_input_energy
slave: *multiplus_unit_id
address: 82
input_type: holding
data_type: uint32
scale: 0.01
precision: 2
unit_of_measurement: "kWh"
device_class: energy
state_class: total_increasing
- name: MultiPlus Battery to AC Input Energy
unique_id: multiplus_battery_to_ac_input_energy
slave: *multiplus_unit_id
address: 86
input_type: holding
data_type: uint32
scale: 0.01
precision: 2
unit_of_measurement: "kWh"
device_class: energy
state_class: total_increasing
- name: MultiPlus Battery to AC Output Energy
unique_id: multiplus_battery_to_ac_output_energy
slave: *multiplus_unit_id
address: 90
input_type: holding
data_type: uint32
scale: 0.01
precision: 2
unit_of_measurement: "kWh"
device_class: energy
state_class: total_increasing
- name: MultiPlus AC Output to Battery Energy
unique_id: multiplus_ac_output_to_battery_energy
slave: *multiplus_unit_id
address: 92
input_type: holding
data_type: uint32
scale: 0.01
precision: 2
unit_of_measurement: "kWh"
device_class: energy
state_class: total_increasing
- name: MultiPlus PowerAssist Boost Factor
unique_id: multiplus_powerassist_boost_factor
slave: *multiplus_unit_id
address: 106
input_type: holding
data_type: uint16
scale: 0.01
precision: 2
state_class: measurement state_class: measurement
unique_id: charger_ac_current_limit
- name: MultiPlus Configured Output Voltage - name: Charger On Off Raw
unique_id: multiplus_configured_output_voltage slave: 223
slave: *multiplus_unit_id address: 2317
address: 107
input_type: holding input_type: holding
data_type: uint16 data_type: uint16
scale: 0.01
precision: 2
unit_of_measurement: "V"
device_class: voltage
state_class: measurement
- name: MultiPlus PowerAssist Enabled Raw
unique_id: multiplus_powerassist_enabled_raw
slave: *multiplus_unit_id
address: 108
input_type: holding
data_type: uint16
- name: MultiPlus UPS Function Raw
unique_id: multiplus_ups_function_raw
slave: *multiplus_unit_id
address: 109
input_type: holding
data_type: uint16
- name: MultiPlus Weak AC Input Raw
unique_id: multiplus_weak_ac_input_raw
slave: *multiplus_unit_id
address: 129
input_type: holding
data_type: uint16
- name: MultiPlus Nominal Inverter Power
unique_id: multiplus_nominal_inverter_power
slave: *multiplus_unit_id
address: 130
input_type: holding
data_type: uint32
scale: 1 scale: 1
precision: 0
unit_of_measurement: "W"
device_class: power
state_class: measurement state_class: measurement
unique_id: charger_on_off_raw
- name: Charger Charge State Raw
slave: 223
address: 2318
input_type: holding
data_type: uint16
scale: 1
state_class: measurement
unique_id: charger_charge_state_raw
- name: Charger Error Code
slave: 223
address: 2319
input_type: holding
data_type: uint16
scale: 1
state_class: measurement
unique_id: charger_error_code
- name: Charger Relay State
slave: 223
address: 2320
input_type: holding
data_type: uint16
scale: 1
state_class: measurement
unique_id: charger_relay_state
- name: Charger Low Voltage Alarm
slave: 223
address: 2321
input_type: holding
data_type: uint16
scale: 1
state_class: measurement
unique_id: charger_low_voltage_alarm
- name: Charger High Voltage Alarm
slave: 223
address: 2322
input_type: holding
data_type: uint16
scale: 1
state_class: measurement
unique_id: charger_high_voltage_alarm
template: template:
- sensor: - sensor:
@@ -762,208 +439,34 @@ template:
{{ v100 }} {{ v100 }}
{% endif %} {% endif %}
- name: MultiPlus State - name: Charger On Off
unique_id: multiplus_state
availability: "{{ is_number(states('sensor.multiplus_state_raw')) }}"
state: >- state: >-
{% set v = states('sensor.multiplus_state_raw') | int(-1) %} {% set v = states('sensor.charger_on_off_raw')|int %}
{{ { {{ {0:'Off',1:'On',2:'Error',3:'Unavailable'}.get(v, 'Unknown') }}
0:'Off',1:'Low power',2:'Fault',3:'Bulk',4:'Absorption',5:'Float',
6:'Storage',7:'Equalize',8:'Pass-through',9:'Inverting',
10:'PowerAssist',11:'Power supply',244:'Sustain',252:'External control'
}.get(v, 'Unknown') }}
# Keep the existing charger entities and energy history.
- name: Charger Charge State - name: Charger Charge State
availability: "{{ is_number(states('sensor.multiplus_charge_state_raw')) }}"
state: >- state: >-
{% set v = states('sensor.multiplus_charge_state_raw') | int(-1) %} {% set v = states('sensor.charger_charge_state_raw')|int %}
{{ { {{ {
0:'Initialising',1:'Bulk',2:'Absorption',3:'Float',4:'Storage', 0:'Off',1:'Low Power',2:'Fault',3:'Bulk',4:'Absorption',5:'Float',
5:'Absorption repeat',6:'Forced absorption',7:'Equalize', 6:'Storage',7:'Equalize/Manual',8:'External Control'
8:'Bulk stopped',9:'Unknown' }.get(v,'Unknown') }}
}.get(v, 'Unknown') }}
- name: MultiPlus Active Input
unique_id: multiplus_active_input
availability: "{{ is_number(states('sensor.multiplus_active_input_raw')) }}"
state: >-
{{ {0:'AC input 1',1:'AC input 2',240:'Disconnected'}
.get(states('sensor.multiplus_active_input_raw') | int(-1), 'Unknown') }}
# VE.Bus DC current is positive while charging and negative while inverting.
- name: MultiPlus DC Power
unique_id: multiplus_dc_power
unit_of_measurement: "W"
device_class: power
state_class: measurement
availability: >-
{{ is_number(states('sensor.multiplus_battery_voltage'))
and is_number(states('sensor.multiplus_battery_current')) }}
state: >-
{{ (states('sensor.multiplus_battery_voltage') | float(0)
* states('sensor.multiplus_battery_current') | float(0)) | round(1) }}
- name: "Charger DC Wattage" - name: "Charger DC Wattage"
unique_id: charger_dc_wattage unique_id: charger_dc_wattage
unit_of_measurement: "W" unit_of_measurement: "W"
device_class: power device_class: power
state_class: measurement state_class: measurement
availability: "{{ is_number(states('sensor.multiplus_dc_power')) }}"
state: >- state: >-
{{ [states('sensor.multiplus_dc_power') | float(0), 0] | max }} {% set v = states('sensor.charger_output_1_voltage')|float(0) %}
{% set a = states('sensor.charger_output_1_current')|float(0) %}
- name: MultiPlus Inverter DC Power {{ (v * a) | round(1) }}
unique_id: multiplus_inverter_dc_power
unit_of_measurement: "W"
device_class: power
state_class: measurement
availability: "{{ is_number(states('sensor.multiplus_dc_power')) }}"
state: >-
{{ [0 - states('sensor.multiplus_dc_power') | float(0), 0] | max }}
- binary_sensor: - binary_sensor:
- name: MultiPlus AC Input Connected - name: Charger Low Voltage Alarm Active
unique_id: multiplus_ac_input_connected state: "{{ states('sensor.charger_low_voltage_alarm')|int == 2 }}"
device_class: power - name: Charger High Voltage Alarm Active
# Register 29 identifies the selected input; it alone does not establish state: "{{ states('sensor.charger_high_voltage_alarm')|int == 2 }}"
# that the Multi is using AC. Keep the entity ID, but report active use.
availability: >-
{{ states('sensor.multiplus_active_input_raw') | int(-1) in [0, 1, 240]
and states('sensor.multiplus_mode_raw') | int(-1) in [1, 2, 3, 4]
and states('sensor.multiplus_state_raw') | int(-1)
in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 244, 252] }}
state: >-
{{ states('sensor.multiplus_active_input_raw') | int(-1) in [0, 1]
and states('sensor.multiplus_mode_raw') | int(-1) in [1, 2, 3]
and states('sensor.multiplus_state_raw') | int(-1)
in [3, 4, 5, 6, 7, 8, 10, 11, 244, 252] }}
- name: MultiPlus High Temperature Alarm
unique_id: multiplus_high_temperature_alarm
device_class: problem
availability: >-
{{ states('sensor.multiplus_high_temperature_alarm_raw') | int(-1) in [0, 1, 2] }}
state: "{{ states('sensor.multiplus_high_temperature_alarm_raw') | int(0) > 0 }}"
- name: MultiPlus Low Battery Alarm
unique_id: multiplus_low_battery_alarm
device_class: problem
availability: >-
{{ states('sensor.multiplus_low_battery_alarm_raw') | int(-1) in [0, 1, 2] }}
state: "{{ states('sensor.multiplus_low_battery_alarm_raw') | int(0) > 0 }}"
- name: MultiPlus Overload Alarm
unique_id: multiplus_overload_alarm
device_class: problem
availability: >-
{{ states('sensor.multiplus_overload_alarm_raw') | int(-1) in [0, 1, 2] }}
state: "{{ states('sensor.multiplus_overload_alarm_raw') | int(0) > 0 }}"
- name: MultiPlus Ripple Alarm
unique_id: multiplus_ripple_alarm
device_class: problem
availability: >-
{{ states('sensor.multiplus_ripple_alarm_raw') | int(-1) in [0, 1, 2] }}
state: "{{ states('sensor.multiplus_ripple_alarm_raw') | int(0) > 0 }}"
- name: MultiPlus Temperature Sensor Alarm
unique_id: multiplus_temperature_sensor_alarm
device_class: problem
availability: >-
{{ states('sensor.multiplus_temperature_sensor_alarm_raw') | int(-1) in [0, 1, 2] }}
state: "{{ states('sensor.multiplus_temperature_sensor_alarm_raw') | int(0) > 0 }}"
- name: MultiPlus Voltage Sensor Alarm
unique_id: multiplus_voltage_sensor_alarm
device_class: problem
availability: >-
{{ states('sensor.multiplus_voltage_sensor_alarm_raw') | int(-1) in [0, 1, 2] }}
state: "{{ states('sensor.multiplus_voltage_sensor_alarm_raw') | int(0) > 0 }}"
- name: MultiPlus Grid Lost Alarm
unique_id: multiplus_grid_lost_alarm
device_class: problem
availability: >-
{{ states('sensor.multiplus_grid_lost_alarm_raw') | int(-1) in [0, 2] }}
state: "{{ is_state('sensor.multiplus_grid_lost_alarm_raw', '2') }}"
- name: MultiPlus PowerAssist Enabled
unique_id: multiplus_powerassist_enabled
availability: >-
{{ states('sensor.multiplus_powerassist_enabled_raw') | int(-1) in [0, 1] }}
state: "{{ is_state('sensor.multiplus_powerassist_enabled_raw', '1') }}"
- name: MultiPlus UPS Enabled
unique_id: multiplus_ups_enabled
availability: >-
{{ states('sensor.multiplus_ups_function_raw') | int(-1) in [0, 1] }}
# Victron register 109 uses 0 for enabled and 1 for disabled.
state: "{{ is_state('sensor.multiplus_ups_function_raw', '0') }}"
# These controls read their state from the Multi; they never write on startup.
# GX/Digital Multi Control/VE.Bus BMS settings can restrict remote control.
- select:
- name: MultiPlus Mode
unique_id: multiplus_mode
icon: mdi:power-settings
availability: >-
{{ states('sensor.multiplus_mode_raw') | int(-1) in [1, 2, 3, 4] }}
state: >-
{{ {1:'Charger only',2:'Inverter only',3:'On',4:'Off'}
.get(states('sensor.multiplus_mode_raw') | int(-1), 'Unknown') }}
options: "{{ ['On', 'Charger only', 'Inverter only', 'Off'] }}"
select_option:
- condition: template
value_template: >-
{{ states('sensor.multiplus_mode_raw') | int(-1) in [1, 2, 3, 4]
and option in ['On', 'Charger only', 'Inverter only', 'Off'] }}
- action: modbus.write_register
data:
hub: victron_gx
slave: *multiplus_unit_id
address: 33
value: "{{ {'Charger only':1,'Inverter only':2,'On':3,'Off':4}[option] }}"
- delay:
seconds: 2
- action: homeassistant.update_entity
target:
entity_id:
- sensor.multiplus_mode_raw
- sensor.multiplus_state_raw
- sensor.multiplus_active_input_raw
- number:
- name: MultiPlus AC Input Current Limit
unique_id: multiplus_ac_input_current_limit
icon: mdi:current-ac
device_class: current
unit_of_measurement: "A"
min: 0
# Van shore-power plug rating; this is a UI bound, not a startup setting.
max: 30
step: 0.1
availability: >-
{{ is_number(states('sensor.multiplus_ac_input_current_limit_readback'))
and states('sensor.multiplus_ac_input_current_limit_readback') | float(-1) >= 0 }}
state: "{{ states('sensor.multiplus_ac_input_current_limit_readback') | float(0) }}"
set_value:
- condition: template
value_template: >-
{{ is_number(states('sensor.multiplus_ac_input_current_limit_readback'))
and is_number(value) and 0 <= value | float(-1) <= 30 }}
- action: modbus.write_register
data:
hub: victron_gx
slave: *multiplus_unit_id
address: 22
# Register 22 is in tenths of an amp: 10.8 A -> 108.
value: "{{ (value | float * 10) | round(0) | int }}"
- delay:
seconds: 2
- action: homeassistant.update_entity
target:
entity_id: sensor.multiplus_ac_input_current_limit_readback
sensor: sensor:
- platform: integration - platform: integration
@@ -993,16 +496,6 @@ sensor:
max_sub_interval: max_sub_interval:
minutes: 5 minutes: 5
- platform: integration
source: sensor.multiplus_inverter_dc_power
name: MultiPlus Inverter DC Energy
unique_id: multiplus_inverter_dc_energy
unit_prefix: k
round: 2
method: trapezoidal
max_sub_interval:
minutes: 5
utility_meter: utility_meter:
dc_load_energy_daily: dc_load_energy_daily:
source: sensor.dc_system_energy source: sensor.dc_system_energy
@@ -1027,11 +520,3 @@ utility_meter:
charger_dc_wattage_monthly: charger_dc_wattage_monthly:
source: sensor.dc_charger_energy source: sensor.dc_charger_energy
cycle: monthly cycle: monthly
multiplus_inverter_dc_energy_daily:
source: sensor.multiplus_inverter_dc_energy
cycle: daily
multiplus_inverter_dc_energy_monthly:
source: sensor.multiplus_inverter_dc_energy
cycle: monthly
+96
View File
@@ -0,0 +1,96 @@
# Kanidm on Jeeves
Kanidm is served at **https://auth.tmmworkshop.com**. Portal terminates public
HTTPS and forwards requests over Tailscale to Jeeves on port 8443, verifying
Jeeves's TLS certificate. Only the Tailscale interface exposes that backend port.
The service module is automatically imported by `services/default.nix`.
`services/kanidm.nix` pins Kanidm to the 1.11 release series; review the
[upgrade guide](https://kanidm.github.io/kanidm/stable/server_updates.html)
before changing that series. The flake lock determines the patch release.
## Before deployment
1. Point `auth.tmmworkshop.com` at Portal's public address, using the same DNS
arrangement as `gitea.tmmworkshop.com`. Port 80 must reach Portal for ACME.
2. Enable [HTTPS certificates in Tailscale](https://tailscale.com/kb/1153/enabling-https).
Jeeves must be able to obtain a certificate for `jeeves.taile39c31.ts.net`.
3. Allow Portal to reach Jeeves on TCP 8443 in your Tailscale policy.
The trusted proxy address in `services/kanidm.nix` is Portal's current Tailscale
IPv4 address, `100.80.102.33`. Update it if Portal is re-enrolled with a new address.
HAProxy connects over IPv4 and replaces incoming `X-Forwarded-For` headers.
## Deploy
Ensure the new Nix file is tracked by Git before using this Git-backed flake.
From this repository, using the existing SSH aliases:
```sh
nixos-rebuild switch --flake .#jeeves --target-host jeeves --sudo
nixos-rebuild switch --flake .#portal-1 --target-host portal-1 --sudo
```
Deploy Jeeves first, then Portal. Portal manages the public Let's Encrypt
certificate. `kanidm-tls.service` on Jeeves obtains the backend certificate and
checks for renewal daily, restarting Kanidm only when the certificate changes.
Private keys are generated at runtime and never stored in this repository or
the Nix store.
On Jeeves, check startup:
```sh
sudo systemctl status kanidm-tls.service kanidm.service
sudo journalctl -u kanidm-tls.service -u kanidm.service -n 100
```
If the initial certificate request fails, fix the Tailscale HTTPS configuration
and run `sudo systemctl restart kanidm.service`; its dependency retries the
certificate request. A successful oneshot certificate service normally appears
as inactive after finishing.
After both hosts are deployed:
```sh
curl --fail https://auth.tmmworkshop.com/status
```
## First login
On Jeeves, recover the initial administrator passwords interactively:
```sh
sudo kanidmd recover-account admin
sudo kanidmd recover-account idm_admin
kanidm login --name idm_admin
kanidm person create richie "Richie"
kanidm person credential create-reset-token richie
```
Store the recovered passwords securely. Follow the reset URL to enroll your
personal account's passkey. `admin` manages the Kanidm server; `idm_admin`
manages people, groups, and application integrations. Creating `richie` does
not automatically grant administrator rights. The client is installed and
configured to use `https://auth.tmmworkshop.com`.
Register each website as its own OAuth2/OIDC client and each backend identity
as a service account, then grant application scopes through groups. This
deployment does not create application clients or change existing sites' login
settings. See the [OAuth2 guide](https://kanidm.github.io/kanidm/stable/integrations/oauth2.html)
and [service-account guide](https://kanidm.github.io/kanidm/stable/accounts/service_accounts.html).
## Storage and recovery
- Database: `/var/lib/kanidm/kanidm.db` on Jeeves's existing ZFS `/var` filesystem.
- Online backups: `/var/lib/kanidm/backups`, daily at 02:30, retaining 14.
- Backend certificate and key: `/var/lib/kanidm-tls`, readable by Kanidm.
- Server configuration: `/etc/kanidm/server.toml`.
The existing `root_pool/var` snapshot policy covers the database and backups.
Copy backups off Jeeves as part of disaster recovery; local snapshots do not
protect against loss of the host. Follow Kanidm's
[backup and restore instructions](https://kanidm.github.io/kanidm/stable/backup_and_restore.html).
The configured identity domain and origin are both `auth.tmmworkshop.com`.
Changing them after enrollment can affect passkeys and application integrations;
follow the [domain rename guide](https://kanidm.github.io/kanidm/stable/domain_rename.html).
+2
View File
@@ -8,6 +8,7 @@ let
grafana = 3000; grafana = 3000;
jellyfin_http = 8096; jellyfin_http = 8096;
jellyswarrm = 3100; jellyswarrm = 3100;
kanidm = 8443;
nix_binary_cache = 5000; nix_binary_cache = 5000;
ollama = 11434; ollama = 11434;
open_webui = 8080; open_webui = 8080;
@@ -28,6 +29,7 @@ in
ports.gems ports.gems
ports.jellyfin_http ports.jellyfin_http
ports.jellyswarrm ports.jellyswarrm
ports.kanidm
ports.nix_binary_cache ports.nix_binary_cache
ports.share ports.share
]; ];
+101
View File
@@ -0,0 +1,101 @@
{ config, pkgs, ... }:
let
certificateDir = "/var/lib/kanidm-tls";
in
{
services.kanidm = {
# Pin the minor release: Kanidm upgrades require following its upgrade guide.
package = pkgs.kanidm_1_11;
server = {
enable = true;
settings = {
domain = "auth.tmmworkshop.com";
origin = "https://auth.tmmworkshop.com";
bindaddress = "0.0.0.0:8443";
db_fs_type = "zfs";
tls_chain = "${certificateDir}/certificate.pem";
tls_key = "${certificateDir}/private-key.pem";
# Only Portal may supply the original client address. HAProxy replaces
# X-Forwarded-For, so clients cannot supply a forged address themselves.
http_client_address_info.x-forward-for = [ "100.80.102.33" ];
# /var/lib/kanidm is on root_pool/var, which is already snapshotted.
online_backup = {
schedule = "30 2 * * *";
versions = 14;
};
};
};
client = {
enable = true;
settings.uri = "https://auth.tmmworkshop.com";
};
};
systemd = {
services = {
kanidm = {
requires = [ "kanidm-tls.service" ];
after = [ "kanidm-tls.service" ];
};
# Kanidm requires TLS even behind HAProxy. Use Jeeves's Tailscale
# certificate so Portal can verify both the CA and the backend hostname.
# HTTPS certificates must be enabled in the Tailscale admin console.
kanidm-tls = {
description = "Obtain and renew Kanidm's backend TLS certificate";
requires = [ "tailscaled.service" ];
wants = [ "tailscaled-autoconnect.service" ];
after = [
"tailscaled.service"
"tailscaled-autoconnect.service"
];
path = [
config.services.tailscale.package
pkgs.coreutils
pkgs.systemd
];
serviceConfig = {
Type = "oneshot";
User = "root";
Group = "kanidm";
StateDirectory = "kanidm-tls";
StateDirectoryMode = "0750";
RuntimeDirectory = "kanidm-tls";
RuntimeDirectoryMode = "0700";
UMask = "0077";
TimeoutStartSec = "2min";
};
script = ''
set -euo pipefail
tailscale cert --min-validity 720h \
--cert-file /run/kanidm-tls/certificate.pem \
--key-file /run/kanidm-tls/private-key.pem \
jeeves.taile39c31.ts.net
if ! cmp -s /run/kanidm-tls/certificate.pem ${certificateDir}/certificate.pem \
|| ! cmp -s /run/kanidm-tls/private-key.pem ${certificateDir}/private-key.pem; then
install -m 0640 -o root -g kanidm /run/kanidm-tls/certificate.pem ${certificateDir}/certificate.pem
install -m 0640 -o root -g kanidm /run/kanidm-tls/private-key.pem ${certificateDir}/private-key.pem
# The NixOS service bind-mounts the certificate files, so restart to
# pick up renewals. Do not wait on a service that depends on this one.
if systemctl is-active --quiet kanidm.service; then
systemctl --no-block try-restart kanidm.service
fi
fi
'';
};
};
timers.kanidm-tls = {
wantedBy = [ "timers.target" ];
timerConfig = {
OnCalendar = "daily";
Persistent = true;
RandomizedDelaySec = "1h";
};
};
};
}
+12 -1
View File
@@ -32,7 +32,7 @@ defaults
#Application Setup #Application Setup
frontend ContentSwitching frontend ContentSwitching
bind [::]:80 v4v6 bind [::]:80 v4v6
bind [::]:443 v4v6 ssl crt /var/lib/acme/audiobookshelf.tmmworkshop.com/full.pem crt /var/lib/acme/cache.tmmworkshop.com/full.pem crt /var/lib/acme/gems.tmmworkshop.com/full.pem crt /var/lib/acme/jellyfin.tmmworkshop.com/full.pem crt /var/lib/acme/share.tmmworkshop.com/full.pem crt /var/lib/acme/gitea.tmmworkshop.com/full.pem bind [::]:443 v4v6 ssl crt /var/lib/acme/audiobookshelf.tmmworkshop.com/full.pem crt /var/lib/acme/auth.tmmworkshop.com/full.pem crt /var/lib/acme/cache.tmmworkshop.com/full.pem crt /var/lib/acme/gems.tmmworkshop.com/full.pem crt /var/lib/acme/jellyfin.tmmworkshop.com/full.pem crt /var/lib/acme/share.tmmworkshop.com/full.pem crt /var/lib/acme/gitea.tmmworkshop.com/full.pem
mode http mode http
# ACME challenge routing (must be first) # ACME challenge routing (must be first)
@@ -40,6 +40,7 @@ frontend ContentSwitching
# Host ACLs (defined early so rate-limiting can scope to a single vhost) # Host ACLs (defined early so rate-limiting can scope to a single vhost)
acl host_audiobookshelf hdr(host) -i audiobookshelf.tmmworkshop.com acl host_audiobookshelf hdr(host) -i audiobookshelf.tmmworkshop.com
acl host_auth hdr(host) -i auth.tmmworkshop.com
acl host_cache hdr(host) -i cache.tmmworkshop.com acl host_cache hdr(host) -i cache.tmmworkshop.com
acl host_jellyfin hdr(host) -i jellyfin.tmmworkshop.com acl host_jellyfin hdr(host) -i jellyfin.tmmworkshop.com
acl host_share hdr(host) -i share.tmmworkshop.com acl host_share hdr(host) -i share.tmmworkshop.com
@@ -111,6 +112,7 @@ frontend ContentSwitching
use_backend acme_challenge if is_acme use_backend acme_challenge if is_acme
use_backend audiobookshelf_nodes if host_audiobookshelf use_backend audiobookshelf_nodes if host_audiobookshelf
use_backend kanidm if host_auth
use_backend cache_nodes if host_cache use_backend cache_nodes if host_cache
use_backend jellyfin if host_jellyfin use_backend jellyfin if host_jellyfin
use_backend share_nodes if host_share use_backend share_nodes if host_share
@@ -141,6 +143,15 @@ backend audiobookshelf_nodes
mode http mode http
server server jeeves:8000 server server jeeves:8000
backend kanidm
mode http
http-request set-header X-Forwarded-For %[src]
http-request set-header X-Forwarded-Proto https
option httpchk
http-check send meth GET uri /status ver HTTP/1.1 hdr Host auth.tmmworkshop.com
http-check expect status 200
server kanidm jeeves.taile39c31.ts.net:8443 ssl verify required ca-file /etc/ssl/certs/ca-certificates.crt verifyhost jeeves.taile39c31.ts.net sni str(jeeves.taile39c31.ts.net) check check-sni jeeves.taile39c31.ts.net resolve-prefer ipv4
backend cache_nodes backend cache_nodes
mode http mode http
server server jeeves:5000 server server jeeves:5000
@@ -82,7 +82,6 @@
"Michal", "Michal",
"Nornsight", "Nornsight",
"pydantic", "pydantic",
"pyproject",
"sandboxing", "sandboxing",
"syncthing", "syncthing",
"vllm", "vllm",