ma home: deploy home-assistant via docker

This commit is contained in:
makefu 2023-06-03 15:27:46 +02:00
parent dd0a6294c8
commit be32844179
No known key found for this signature in database
GPG Key ID: 36F7711F3FC0F225
21 changed files with 358 additions and 197 deletions

View File

@ -1,8 +1,12 @@
{ pkgs, ... }:
let
#dev = "/dev/web_cam";
dev = "/dev/video0";
in
{
services.mjpg-streamer = {
enable = true;
inputPlugin = "input_uvc.so -d /dev/web_cam -r 1280x960";
inputPlugin = "input_uvc.so -d ${dev} -r 1280x960";
};
users.users.octoprint.extraGroups = [ "video" ];
# allow octoprint to access /dev/vchiq

View File

@ -1,10 +1,12 @@
let
inherit (import ../lib) btn_cycle_light;
schlafzimmer_komode = "light.schlafzimmer_komode_osram";
schlafzimmer_button = "sensor.schlafzimmer_btn2_click";
in {
services.home-assistant.config.automation = [
# (btn_cycle_light "light.arbeitszimmerbeleuchtung" "arbeitszimmer_btn1")
(btn_cycle_light "light.schlafzimmer_komode_osram" "schlafzimmer_btn2" 128)
{
alias = "toggle keller";
trigger = {
@ -32,21 +34,35 @@ in {
service = "light.toggle";
data = {
entity_id = "light.keller_osram";
brightness = 50;
brightness = 25;
};
};
}
# (btn_cycle_light "light.wohnzimmerbeleuchtung" "wohnzimmer_btn3")
{
alias = "Turn of all lights via schlafzimmer_btn2 double click";
alias = "Dim Toggle schlafzimmer komode";
trigger = {
platform = "state";
entity_id = "sensor.schlafzimmer_btn2_click";
entity_id = schlafzimmer_button;
to = "single";
};
action = {
service = "light.toggle";
entity_id = schlafzimmer_komode;
brightness = 1;
};
}
{
alias = "Bright Toggle schlafzimmer komode";
trigger = {
platform = "state";
entity_id = schlafzimmer_button;
to = "double";
};
action = {
service = "light.turn_off";
entity_id = "all";
service = "light.toggle";
entity_id = schlafzimmer_komode;
brightness = 255;
};
}
];

View File

@ -6,7 +6,7 @@
let
schranklicht = [
"light.wohnzimmer_schrank_osram"
"light.wohnzimmer_komode_osram"
# "light.wohnzimmer_komode_osram"
];
weihnachtslicht = "light.wohnzimmer_fenster_lichterkette_licht";
fernsehlicht = "light.wled";
@ -31,8 +31,8 @@ in
automation =
[
(turn_on schranklicht "-00:30:00")
#(turn_on weihnachtslicht "-00:30:00")
(turn_on fernsehlicht "-00:00:00")
(turn_on weihnachtslicht "-00:00:00")
#(turn_on fernsehlicht "-00:00:00")
{ alias = "Always turn off the urlaub lights at ${final_off}";
trigger = [

View File

@ -7,7 +7,7 @@ Heute ist {{ weekday }}, du solltest gar nicht arbeiten!
{% else %}
Willkommen auf Arbeit Felix.
{% endif -%}
Das aktuell gewählte Projekt ist {{ states("sensor.felix_project") }}.
Dein Projekt ist {{ states("sensor.felix_project") }}.
{% set inside = states("sensor.wohnzimmer_temp_temperature") | float | round(2) -%}
{% set outside = states("sensor.dark_sky_temperature") | float | round(2) -%}

View File

@ -17,6 +17,7 @@ in {
./zigbee2mqtt.nix
# ./multi/flurlicht.nix
./multi/kurzzeitwecker.nix
./intents
./multi/the_playlist.nix
./multi/heizung.nix
# ./multi/fliegen-couter.nix
@ -92,6 +93,7 @@ in {
{ type = "homeassistant"; }
];
};
tasmota = {};
binary_sensor = [
{ platform = "workday";
name = "Arbeitstag";

View File

@ -0,0 +1,30 @@
{ config, pkgs, lib, ... }:
let
confdir = "/var/lib/homeassistant-docker";
in {
imports = [
./nginx.nix
./mqtt.nix
./signal-rest
./signal-rest/service.nix
];
networking.firewall.allowedTCPPorts = [ 8123 ];
state = [ "/var/lib/hass/known_devices.yaml" ];
virtualisation.oci-containers.containers.hass = {
image = "homeassistant/home-assistant:latest";
environment = {
TZ = "Europe/Berlin";
UMASK = "007";
};
extraOptions = ["--net=host" ];
volumes = [
"${confdir}:/config"
#"/data/music:/config/media"
];
};
systemd.tmpfiles.rules = [
#"f ${confdir}/docker-run 0770 kiosk kiosk - -"
"d ${confdir} 0770 kiosk kiosk - -"
];
}

View File

@ -0,0 +1,35 @@
{
services.home-assistant.config = {
intent_script = {
GetTime.speech.text = ''
Es ist {{ now().hour }} Uhr {{ now().minute }}
'';
GutenMorgen.speech.text = ''
Einen wunderschönen Guten Morgen wünsche ich dir
'';
WieGehtEsDir.speech.text = ''
Mir geht es sehr gut, und dir?
'';
Statusreport.speech.text = builtins.readFile ./statusbericht.txt.j2;
StartMusic = {
speech.text = "Spiele {{ music }} musik";
action_async = [
{
service = "media_player.play_media";
data_template = {
entity_id = "media_player.{{ _intent.siteId }}";
media_content_id = builtins.readFile ./music_chooser.txt.j2;
media_content_type = "music";
};
}
];
};
GetWeather = {
#speech.text = ''
# {{ states('sensor.openweathermap_weather') }} bei {{ states('sensor.openweathermap_temperature') }} Grad
#'';
speech.text = "{{ states('sensor.swr_prognose') }}";
};
};
};
}

View File

@ -0,0 +1,13 @@
{% if music == "lounge" -%}
https://cast1.asurahosting.com/proxy/julien/stream.mp3
{% elif music == "lassulus" -%}
http://radio.lassul.us:8000/radio.mp3
{% elif music == "groove" -%}
http://ice2.somafm.com/groovesalad-128.mp3
{% elif music == "swr3" -%}
https://liveradio.swr.de/sw282p3/swr3/play.mp3
{% elif music == "swr1" -%}
https://liveradio.swr.de/sw282p3/swr1bw/play.mp3
{% elif music == "radio" -%}
https://liveradio.swr.de/sw282p3/swr1bw/play.mp3
{% endif %}

View File

@ -0,0 +1,37 @@
{% set arbeit_heute = is_state("binary_sensor.arbeitstag","on") -%}
{% set weekday = ['Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag','Sonntag'][now().weekday()] -%}
{% set is_friday = now().weekday() == 4 %}
Dies ist deine Persönliche Zusammenfassung
{% set inside = states("sensor.wohnzimmer_temp_temperature") | float | round(2) -%}
{% set outside = states("sensor.dark_sky_temperature") | float | round(2) -%}
{% set arbeit_morgen = is_state("binary_sensor.arbeitstag_morgen","on") -%}
Die Wetteraussichten: {{ states("sensor.dark_sky_hourly_summary") | replace(".","")}} bei {{ states("sensor.dark_sky_temperature") }} Grad mit {{ states("sensor.dark_sky_humidity") | round(0) }}% Luftfeuchtigkeit.
{% if states("calendar.abfall_papiermuell") == "on" %}
Heute ist Papiermuell, bring noch schnell dein Papier raus
{% endif %}
{% if states("calendar.abfall_restmuell") == "on" %}
Ausserdem ist heute Restmuell.
{% endif -%}
{% if ( outside < inside ) and ( outside > 18 ) %}
Draussen ist es gerade {{ ((inside - outside) | round(1) )}} gerade kühler
{% endif -%}
{% set current_count = state_attr("sensor.dwd_weather_warnings_current_warning_level", "warning_count") %}
{% for i in range(current_count) %}
{% set idx = i + 1 %}
{% set headline = state_attr("sensor.dwd_weather_warnings_current_warning_level", "warning_" ~ idx ~ "_headline") %}
{% set description = state_attr("sensor.dwd_weather_warnings_current_warning_level", "warning_" ~ idx ~ "_description") %}
{% set level = state_attr("sensor.dwd_weather_warnings_current_warning_level", "warning_" ~ idx ~ "_level") %}
{% set time_start = state_attr("sensor.dwd_weather_warnings_current_warning_level", "warning_" ~ idx ~ "_start") %}
{% set time_end = state_attr("sensor.dwd_weather_warnings_current_warning_level", "warning_" ~ idx ~ "_end") %}
Wetterwarnung {{idx}}: {{ headline }} Stufe {{level}} von {{ time_start.strftime("%H:%M") ~ " bis " ~ time_end.strftime("%H:%M") }} Uhr
{{ description }}
{% endfor %}
{% if is_friday %}
Endlich ist Freitag!
{% endif -%}

View File

@ -27,12 +27,11 @@ in
#}
{ delay.seconds = 1; }
{ delay = ''
{% set duration = state_attr("${entity}","media_duration") %}
{% set seconds = duration % 60 %}
{% set duration = state_attr("${entity}","media_duration") or 0 %}
{% set seconds = (duration % 60 ) %}
{% set minutes = (duration / 60)|int % 60 %}
{% set hours = (duration / 3600)|int %}
{{ "%02i:%02i:%02i"|format(hours, minutes, seconds)}}
'';
}
{

View File

@ -6,10 +6,30 @@ let
wohnzimmer_deko = [
"light.wohnzimmer_fernseher_led_strip" # led um fernseher
"light.wohnzimmer_lichterkette_led_strip" # led um fernsehwand
"light.kinderzimmer_lichterkette_licht" # led um fenster
"light.wohnzimmer_fenster_lichterkette_licht" # led um fenster
];
in {
imports = [ ./tint_wohnzimmer.nix ];
services.home-assistant.config.scene = [
{ name = "Wohnzimmer Abendlicht";
id = "living_room_evening";
entities = {
"light.wohnzimmer_komode_osram_light" = {
state = "on";
brightness = 128;
};
"light.wohnzimmer_schrank_osram_light" = {
state = "on";
brightness = 128;
};
"light.wohnzimmer_fenster_lichterkette_licht" = "on";
"light.wohnzimmer_fernseher_led_strip" = {
state = "on";
};
};
}
];
services.home-assistant.config.wled = {};
services.home-assistant.config.light = [
{
@ -22,6 +42,11 @@ in {
name = "Wohnzimmer Deko";
entities = wohnzimmer_deko;
}
{
platform = "group";
name = "living_room_lights";
entities = wohnzimmerbeleuchtung ++ wohnzimmer_deko;
}
];
}

View File

@ -3,11 +3,11 @@ let
in {
services.home-assistant.config = {
notify = [
{
platform = "nfandroidtv";
name = "FireTV Wohnzimmer Notification";
host = firetv_stick;
}
#{
#platform = "nfandroidtv";
#name = "FireTV Wohnzimmer Notification";
#host = firetv_stick;
#}
];
media_player = [
#{
@ -16,12 +16,12 @@ in {
# host = firetv_stick;
#}
# Configuration needs to be done by hand via web interface "integration"
{ platform = "androidtv";
name = "FireTV Stick Android";
device_class = "firetv";
host = firetv_stick;
port = 5555;
}
#{ platform = "androidtv";
# name = "FireTV Stick Android";
# device_class = "firetv";
# host = firetv_stick;
# port = 5555;
#}
];
};
}

View File

@ -5,7 +5,7 @@
services.mosquitto = {
enable = true;
persistence = false;
settings.max_keepalive = 60;
settings.max_keepalive = 1060;
listeners = [
{
port = 1883;

View File

@ -9,128 +9,80 @@
let
button = "sensor.zigbee_btn2_click";
notify = "notify.signal_home";
# für {{ _intent.siteId }} - name of the rhasspy instance: arbeitszimmer
in
{
services.home-assistant.config = {
timer.kurzzeitwecker =
{
name = "Zigbee Kurzzeitwecker";
duration = 300;
automation = [];
timer.kurzzeitwecker = {
name = "Wecker Wohnung";
};
script.add_5_minutes_to_kurzzeitwecker =
timer.wecker_arbeitszimmer = {
name = "Wecker Arbeitszimmer";
};
timer.wecker_wohnzimmer = {
name = "Wecker Wohnzimmer";
};
intent = {};
intent_script = {
TimerjobStart = {
speech.text = ''
{% set h = hours|default('0')|string %}
{% set m = minutes|default('0')|string %}
{% if h == "0" %}
Wecker gestellt {{ m }} Minuten
{% elif m == "0" %}
Wecker gestellt {{ h }} Stunden
{% else %}
Wecker gestellt {{ h }} Stunden und {{ m }} Minuten
{% endif %}
'';
action = [
{
alias = "Add 5 minutes to kurzzeitwecker";
sequence = [
{ service = "timer.pause";
entity_id = "timer.kurzzeitwecker";
service = "timer.start";
data.entity_id = "timer.kurzzeitwecker";
data.duration = ''
{% set h = hours|default("0")|int %}
{% set m = minutes|default("0")|int %}
{{ "%02d" | format(h) }}:{{ "%02d" | format(m) }}:00
'';
}
{ service = "timer.start";
data_template = {
entity_id = "timer.kurzzeitwecker";
duration = ''
{% set r = state_attr('timer.kurzzeitwecker', 'remaining') ~ '-0000' %}
{% set t = strptime(r, '%H:%M:%S.%f%z') %}
{{ (as_timestamp(t) + 300) | timestamp_custom('%H:%M:%S', false) }}
];
};
TimerjobRemaining = {
speech.text = ''
{% set timer = states('timer.kurzzeitwecker') %}
{% if timer == 'idle' %}
Wecker läuft nicht
{% elif timer == 'active' %}
{% set remaining = as_timestamp( state_attr('timer.kurzzeitwecker','finishes_at') )-( as_timestamp(now())) %}
{% set s = ((remaining % 60)) | int %}
{% set m = ((remaining % 3600) / 60) | int %}
{% set h = ((remaining % 86400) / 3600) | int %}
{% if h == 0 %}
Es verbleiben {{ m }} Minuten und {{ s }} Sekunden
{% elif m == 0 %}
Es verbleiben {{ h }} Stunden
{% elif m == 0 and h == 0 %}
Es verbleiben {{ s }} Sekunden
{% else %}
Es verbleiben {{ h }} Stunden {{ m }} Minuten
{% endif %}
{% endif %}
'';
};
}
];
};
automation =
[
{
alias = "Start Timer 5min";
trigger = {
platform = "state";
entity_id = button;
to = "single";
};
condition =
{ condition = "state";
entity_id = "timer.kurzzeitwecker";
state = "idle";
};
TimerjobStop = {
speech.text = ''
Wecker gestoppt
'';
action = [
{ service = "timer.start";
entity_id = "timer.kurzzeitwecker";
data.duration = "00:05:00";
}
{
service = notify;
data.message = "Timer gestartet {{state_attr('timer.kurzzeitwecker', 'remaining') }}, verbleibend ";
{ service = "timer.cancel";
data.entity_id = "timer.kurzzeitwecker";
}
];
}
{
alias = "Add Timer 5min";
trigger = {
platform = "state";
entity_id = button;
to = "single";
};
condition =
{ condition = "state";
entity_id = "timer.kurzzeitwecker";
state = "active";
};
action = [
{ service = "homeassistant.turn_on";
entity_id = "script.add_5_minutes_to_kurzzeitwecker";
}
{
service = notify;
data.message = ''Timer um 5 minuten verlängert, {{ state_attr('timer.kurzzeitwecker', 'remaining') | truncate(9,True," ") }} verbleibend '';
}
];
}
{
alias = "Stop timer on double click";
trigger = [
{
platform = "state";
entity_id = button;
to = "double";
}
{
platform = "state";
entity_id = button;
to = "triple";
}
];
condition =
{
condition = "state";
entity_id = "timer.kurzzeitwecker";
state = "active";
};
action = [
{
service = "timer.cancel";
entity_id = "timer.kurzzeitwecker";
}
{
service = notify;
data.message = "Timer gestoppt, abgebrochen";
}
];
}
{
alias = "Timer Finished";
trigger = {
platform = "event";
event_type = "timer.finished";
event_data.entity_id = "timer.kurzzeitwecker";
};
action = [
{
service = notify;
data.message = "Timer beendet";
}
];
}
];
};
}

View File

@ -40,5 +40,16 @@
{ platform = "accuweather";
api_key = "!secret accuweather";
}
{ platform = "scrape";
resource = "https://www.swr.de/wetter/wetter-liste-swr-100.html";
name = "SWR Prognose";
select = "p[data-refresh=\"weather-headline\"]";
}
{ platform = "scrape";
resource = "https://www.swr.de/wetter/wetter-liste-swr-100.html";
name = "SWR Prognose Langtext";
select = "p[data-refresh=\"weather-text\"]";
}
];
}

View File

@ -1,66 +1,34 @@
{ lib, config, ... }:
let
port = 8096;
in
{
services.jellyfin.enable = true;
services.jellyfin.openFirewall = true;
# services.jellyfin.openFirewall = true;
networking.firewall.interfaces.wiregrill = {
allowedTCPPorts = [ 80 port 8920 ];
allowedUDPPorts = [ 1900 7359 ];
};
state = [ "/var/lib/jellyfin" ];
users.users.${config.services.jellyfin.user}.extraGroups = [ "download" "video" "render" ];
systemd.services.jellyfin = {
after = [ "media-cloud.mount" ];
serviceConfig = rec {
RequiresMountFor = [ "/media/cloud" ];
SupplementaryGroups = lib.mkForce [ "video" "render" "download" ];
UMask = lib.mkForce "0077";
Type = lib.mkForce "simple";
StateDirectory = lib.mkForce "jellyfin";
StateDirectoryMode = lib.mkForce "0700";
CacheDirectory = lib.mkForce "jellyfin";
CacheDirectoryMode = lib.mkForce "0700";
WorkingDirectory = lib.mkForce "/var/lib/jellyfin";
Restart = lib.mkForce "on-failure";
TimeoutSec = lib.mkForce 15;
SuccessExitStatus = lib.mkForce ["0" "143"];
# Security options:
NoNewPrivileges = lib.mkForce true;
SystemCallArchitectures = lib.mkForce "native";
# AF_NETLINK needed because Jellyfin monitors the network connection
RestrictAddressFamilies = lib.mkForce [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ];
RestrictNamespaces = lib.mkForce false;
RestrictRealtime = lib.mkForce true;
RestrictSUIDSGID = lib.mkForce true;
ProtectControlGroups = lib.mkForce false;
ProtectHostname = lib.mkForce true;
ProtectKernelLogs = lib.mkForce false;
ProtectKernelModules = lib.mkForce false;
ProtectKernelTunables = lib.mkForce false;
LockPersonality = lib.mkForce true;
PrivateTmp = lib.mkForce false;
# needed for hardware accelaration
PrivateDevices = lib.mkForce false;
PrivateUsers = lib.mkForce true;
RemoveIPC = lib.mkForce true;
SystemCallFilter = lib.mkForce [
"~@clock"
"~@aio"
"~@chown"
"~@cpu-emulation"
"~@debug"
"~@keyring"
"~@memlock"
"~@module"
"~@mount"
"~@obsolete"
"~@privileged"
"~@raw-io"
"~@reboot"
"~@setuid"
"~@swap"
};
};
services.nginx.virtualHosts."jelly" = {
serverAliases = [
"jelly.lan" "movies.lan"
"jelly.makefu.w" "makefu.omo.w"
];
SystemCallErrorNumber = lib.mkForce "EPERM";
locations."/" = {
proxyPass = "http://localhost:${toString port}";
proxyWebsockets = true;
};
};
}

View File

@ -9,8 +9,7 @@ in
MusicFolder = "/media/cryptX/music/kinder";
Address = "0.0.0.0";
};
systemd.services.navidrome.after = [ "media-cryptX.mount" "cryptsetup.target"
"local-fs.target" "remote-fs.target" ];
systemd.services.navidrome.serviceConfig.RequiresMountFor = [ "/media/cryptX" ];
state = [ "/var/lib/navidrome" ];
# networking.firewall.allowedTCPPorts = [ 4040 ];

View File

@ -70,15 +70,18 @@ in
PHOTOPRISM_HTTP_PORT = port; # Built-in Web server port
PHOTOPRISM_HTTP_COMPRESSION = "gzip"; # Improves transfer speed and bandwidth utilization (none or gzip)
PHOTOPRISM_DEBUG = "false"; # Run in debug mode (shows additional log messages)
PHOTOPRISM_PUBLIC = "true"; # No authentication required (disables password protection)
# PHOTOPRISM_PUBLIC = "true"; # No authentication required (disables password protection)
PHOTOPRISM_READONLY = "false"; # Don't modify originals directory (reduced functionality)
PHOTOPRISM_EXPERIMENTAL = "true"; # Enables experimental features
PHOTOPRISM_DISABLE_WEBDAV = "false"; # Disables built-in WebDAV server
# PHOTOPRISM_DISABLE_WEBDAV = "false"; # Disables built-in WebDAV server
PHOTOPRISM_DISABLE_SETTINGS = "false"; # Disables Settings in Web UI
PHOTOPRISM_DISABLE_TENSORFLOW = "false"; # Disables using TensorFlow for image classification
PHOTOPRISM_DARKTABLE_PRESETS = "false"; # Enables Darktable presets and disables concurrent RAW conversion
PHOTOPRISM_DETECT_NSFW = "false"; # Flag photos as private that MAY be offensive (requires TensorFlow)
PHOTOPRISM_UPLOAD_NSFW = "true"; # Allow uploads that MAY be offensive
PHOTOPRISM_AUTH_MODE = "password";
PHOTOPRISM_ADMIN_USER = "admin";
PHOTOPRISM_ADMIN_PASSWORD = "admin";
#PHOTOPRISM_DATABASE_DRIVER = "postgres";
#PHOTOPRISM_DATABASE_SERVER = "postgres-prism:5432";

View File

@ -0,0 +1,40 @@
{ lib,config, ... }:
# uses alsa instead of pulseaduio server
let
profiles = "/var/lib/rhasspy";
in
{
systemd.services.docker-rhasspy.after = [ "network-online.target" ];
virtualisation.oci-containers.containers.rhasspy = {
image = "rhasspy/rhasspy:latest";
environment = {
TZ = "Europe/Berlin";
PULSE_SERVER = "tcp:${ config.krebs.build.host.name }:4713";
};
ports = [
"12101:12101"
];
volumes = [
"/etc/localtime:/etc/localtime:ro"
"${profiles}:/profiles"
];
cmd = [ "--user-profiles" "/profiles" "--profile" "de" ];
extraOptions = [
"--device=/dev/snd:/dev/snd"
"--group-add=audio"
];
};
systemd.tmpfiles.rules = [
"d ${profiles} 0770 root root - -"
];
# required to allow rhasspy to connect to pulse server
# hardware.pulseaudio.enable = lib.mkForce false;
networking.firewall.allowedTCPPorts = [ 4713 ];
}

View File

@ -0,0 +1,23 @@
{ pkgs, ... }:
let
cfg = pkgs.writeText "hcl-config.json" (builtins.toJSON {
engine = "rhasspy";
pathToConfig = "/var/lib/rhasspy/de/profile.json";
hardware = "respeaker4MicArray";
pattern = "fake-name";
enableDoA = false;
});
in {
systemd.services.HermesLedControl = {
description = "Led Server for ReSpeaker 4-array";
after = [ "network-online.target" "docker-rhasspy.service" ] ;
wantedBy = [ "multi-user.target" ];
serviceConfig = {
# User = "nobody"; # need a user with permissions to run nix-shell
ExecStart = "${pkgs.HermesLedControl}/bin/HermesLedControl --hermesLedControlConfig=${toString cfg}";
Restart = "always";
RestartSec = 10;
PrivateTmp = true;
};
};
}

View File

@ -32,6 +32,10 @@ in
include_device_information = true;
client_id = "zigbee2mqtt";
};
availability = {
active.timeout = 10;
passive.timeout = 1500;
};
frontend = {
port = webport;
};