diff --git a/roles/airconnect/defaults/main.yml b/roles/airconnect/defaults/main.yml new file mode 100644 index 0000000..d9a2267 --- /dev/null +++ b/roles/airconnect/defaults/main.yml @@ -0,0 +1,18 @@ +--- + +airconnect_dir: "/var/lib/airconnect" +airconnect_user: + name: airconnect + uid: 1337 +airconnect_group: + name: airconnect + gid: 1337 + +airconnect_upnp: [] +airconnect_containers: + # UPnP/Sonos + - prog: airupnp + state: started + # Chromecast + - prog: aircast + state: started diff --git a/roles/airconnect/handlers/main.yml b/roles/airconnect/handlers/main.yml new file mode 100644 index 0000000..9d28fe2 --- /dev/null +++ b/roles/airconnect/handlers/main.yml @@ -0,0 +1,11 @@ +--- + +- name: restart airconnect containers + docker_container: + name: airconnect-{{ item }} + state: "{{ item.state }}" + restart: item.state == 'started' + with_items: "{{ airconnect_containers }}" + when: + - airconnect_containers is not defined or not airconnect_containers.changed + - item.state == 'started' diff --git a/roles/airconnect/tasks/airconnect-linuxserver.io-image.yaml b/roles/airconnect/tasks/airconnect-linuxserver.io-image.yaml new file mode 100644 index 0000000..9a032bb --- /dev/null +++ b/roles/airconnect/tasks/airconnect-linuxserver.io-image.yaml @@ -0,0 +1,87 @@ +--- + +- name: create airconnect dir + file: + path: "{{ airconnect_dir }}" + state: directory + mode: "0755" + owner: hass + group: hass + tags: + - airconnect-dirs + +- name: airconnect config files + template: + src: "{{ item.name }}.j2" + dest: "{{ airconnect_dir }}/{{ item.name }}" + owner: "{{ item.owner | default(systemuserlist.hass.uid) }}" + group: "{{ item.group | default(systemuserlist.hass.gid) }}" + mode: "{{ item.mode }}" + notify: restart airconnect container + with_items: + - name: airupnp.xml + mode: "0644" + - name: run + mode: "0755" + - name: supervisord.conf + mode: "0644" + owner: "root" + group: "root" + # needed to nuke the script that would otherwise + # overwrite our sane supervisord.conf file + - name: 30-install + mode: "0755" + loop_control: + label: "{{ item.name }}" + tags: + - airconnect-config + - airconnect + +- name: start airconnect container + docker_container: + name: airconnect + hostname: airconnect + image: 1activegeek/airconnect + #user: "{{ systemuserlist.hass.uid }}:{{ systemuserlist.hass.gid }}" + detach: true + pull: true + auto_remove: false + restart_policy: "unless-stopped" + state: "{{ airconnect_container_state | default('started') }}" + network_mode: host + env: + # docker image uses the linuxserver base image and supervisor, + # setting 'user:' doesnt work because of all of the custom shit + # that the base image does: https://github.com/linuxserver/docker-baseimage-ubuntu/blob/bionic/root/etc/cont-init.d/10-adduser + # but this image doesnt follow that properly, and supervisor starts + # the airconnect processes as root, so we need to mount a custom + # supervisord.conf file instead + PUID: "{{ systemuserlist.hass.uid }}" + PGID: "{{ systemuserlist.hass.gid }}" + + # the image runs a script + # AIRUPNP_VAR: "-x /etc/airconnect/airupnp.xml" + # will create a reference config file for chromecast when found, + # but the defaults are fine, so no need to maintain a custom file + #AIRCAST_VAR: "-i /etc/airconnect/aircast.xml" + mounts: + # generate the reference config files by setting AIRUPNP_VAR and + # AIRCAST_VAR env to '-i $PATH' + - type: bind + source: "{{ airconnect_dir }}/airupnp.xml" + target: /etc/airupnp.xml + read_only: true + # need this to set uid/gid properly + - type: bind + source: "{{ airconnect_dir }}/supervisord.conf" + target: /etc/supervisord.conf + # needed to nuke the script that would otherwise overwrite our sane + # version of the supervisord.conf file when the container starts + - type: bind + source: "{{ airconnect_dir }}/30-install" + target: /etc/cont-init.d/30-install + tags: + - airconnect + - airconnect-container + - docker-containers + register: airconnect_container diff --git a/roles/airconnect/tasks/airconnect.yml b/roles/airconnect/tasks/airconnect.yml new file mode 100644 index 0000000..7480ec7 --- /dev/null +++ b/roles/airconnect/tasks/airconnect.yml @@ -0,0 +1,56 @@ +--- + +- name: create airconnect dir + file: + path: "{{ airconnect_dir }}" + state: directory + mode: "0755" + owner: "{{ owntone_user.uid }}" + group: "{{ owntone_group.gid }}" + tags: + - airconnect-dirs + +- name: airconnect config files + template: + src: "{{ item.name }}.j2" + dest: "{{ airconnect_dir }}/{{ item.name }}" + owner: "{{ owntone_user.uid }}" + group: "{{ owntone_group.gid }}" + mode: "{{ item.mode | default('0644') }}" + notify: restart airconnect containers + with_items: + - name: airupnp.xml + loop_control: + label: "{{ item.name }}" + tags: + - airconnect-config + - airconnect + +- name: "set up the airconnect containers" + docker_container: + name: airconnect-{{ item.prog }} + hostname: airconnect-{{ item.prog }} + image: git.sudo.is/ben/airconnect + detach: true + pull: true + auto_remove: false + restart_policy: "unless-stopped" + state: "{{ item.state | default('started') }}" + network_mode: host + user: "{{ owntone_user.uid }}:{{ owntone_group.gid }}" + env: + AIRCONNECT_PROG: "{{ item.prog }}" + AIRCONNECT_ARGS: "{{ item.args|default() }}" + mounts: + - type: bind + source: "{{ airconnect_dir }}/airupnp.xml" + target: /etc/airupnp.xml + read_only: true + tags: + - airconnect + - airconnect-container + - docker-containers + register: airconnect_containers + loop_control: + label: airconnect-{{ item.prog }} + with_items: "{{ airconnect_containers }}" diff --git a/roles/airconnect/tasks/main.yml b/roles/airconnect/tasks/main.yml new file mode 100644 index 0000000..bf08e23 --- /dev/null +++ b/roles/airconnect/tasks/main.yml @@ -0,0 +1,3 @@ +--- + - import_tasks: airconnect.yml + tags: airconnect diff --git a/roles/airconnect/templates/airupnp.xml.j2 b/roles/airconnect/templates/airupnp.xml.j2 new file mode 100644 index 0000000..5b2b84e --- /dev/null +++ b/roles/airconnect/templates/airupnp.xml.j2 @@ -0,0 +1,37 @@ + + + + + http-get:*:audio/L16;rate=44100;channels=2:DLNA.ORG_PN=LPCM;DLNA.ORG_OP=00;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=0d500000000000000000000000000000 + http-get:*:audio/wav:DLNA.ORG_OP=00;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=0d500000000000000000000000000000 + http-get:*:audio/flac:DLNA.ORG_OP=00;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=0d500000000000000000000000000000 + http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=00;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=0d500000000000000000000000000000 + + 1 + 100 + -1 + 1 + mp3:320 + 1 + 1 + + 0:1000 + 0 + + info + info + warn + info + -1 + 32 + ? + 0:0 + {% for item in airconnect_upnp -%} + + uuid:{{ item.local_uid }} + {{ item.name }} + {{ item.mac }} + {% if item.enabled|default(true) %}1{% else %}0{% endif %} + + {% endfor %} + diff --git a/roles/airconnect/templates/linuxserver.io/30-install.j2 b/roles/airconnect/templates/linuxserver.io/30-install.j2 new file mode 100644 index 0000000..26f3829 --- /dev/null +++ b/roles/airconnect/templates/linuxserver.io/30-install.j2 @@ -0,0 +1,5 @@ +#!/usr/bin/with-contenv bash + +echo "this file has been nuked, so our sane supervisord.conf file is not overwritten" + +exit 0 diff --git a/roles/airconnect/templates/linuxserver.io/run.j2 b/roles/airconnect/templates/linuxserver.io/run.j2 new file mode 100644 index 0000000..63ceade --- /dev/null +++ b/roles/airconnect/templates/linuxserver.io/run.j2 @@ -0,0 +1,3 @@ +#!/usr/bin/with-contenv bash + +supervisord --configuration /etc/supervisord.conf diff --git a/roles/airconnect/templates/linuxserver.io/supervisord.conf.j2 b/roles/airconnect/templates/linuxserver.io/supervisord.conf.j2 new file mode 100644 index 0000000..b1abdee --- /dev/null +++ b/roles/airconnect/templates/linuxserver.io/supervisord.conf.j2 @@ -0,0 +1,25 @@ +[supervisord] +nodaemon=true + +# the base image of the airconnect image uses the env vars $PUID and $PGID +# to create a user, and those are set in the ansible role +# +# supervisord doesnt have an argument for group, uses the default +# group of the user instead. +# +# but supervisor itself needs to run as root because of +# asinine stuff that the linuxserver base image wants to do + +[program:airupnp] +user={{ systemuserlist.hass.uid }} +redirect_stderr=true +# the config file maintained in this ansible role, mounted in +# the docker_container task +command=/bin/airupnp-linux-x86_64 -x /etc/airupnp.xml +process_name = airupnp-linux-x86_64 + +[program:aircast] +user={{ systemuserlist.hass.uid }} +redirect_stderr=true +command=/bin/aircast-linux-x86_64 +process_name = aircast-linux-x86_64 diff --git a/roles/audiobookshelf/handlers/main.yml b/roles/audiobookshelf/handlers/main.yml new file mode 100644 index 0000000..edde81c --- /dev/null +++ b/roles/audiobookshelf/handlers/main.yml @@ -0,0 +1,6 @@ +--- + +- name: reload nginx + service: + name: nginx + state: reloaded diff --git a/roles/audiobookshelf/tasks/audiobookshelf.yml b/roles/audiobookshelf/tasks/audiobookshelf.yml new file mode 100644 index 0000000..c66e5f1 --- /dev/null +++ b/roles/audiobookshelf/tasks/audiobookshelf.yml @@ -0,0 +1,82 @@ +--- + +- name: create dir structure + file: + path: "{{ audiobookshelf_path }}/{{ item.name }}" + mode: "{{ item.mode | default('0750') }}" + owner: "{{ audiobookshelf_user.uid }}" + group: "{{ audiobookshelf_group.gid }}" + tags: + - audiobookshelf-dirs + loop_control: + label: "{{ item.name }}" + with_items: + - name: '' + - name: audiobooks + - name: config + mode: "0755" + - name: metadata + - name: podcasts + +- name: install certs + copy: + src: "/usr/local/etc/letsencrypt/live/{{ item }}" + dest: "/usr/local/etc/certs/" + owner: root + group: root + mode: 0755 + tags: + - letsencrypt-certs + notify: reload nginx + vars: + prediff_cmd: echo + with_items: + - "{{ audiobookshelf_url }}" + +- name: template nginx vhost + template: + src: 02-audiobookshelf.conf.j2 + dest: /etc/nginx/sites-enabled/02-audiobookshelf.conf + owner: root + group: root + mode: 0644 + tags: + - nginx + - audiobookshelf-nginx + notify: reload nginx + +- name: start audiobookshelf container + docker_container: + name: audiobookshelf + auto_remove: false + image: ghcr.io/advplyr/audiobookshelf:latest + detach: true + pull: true + restart_policy: "no" + state: started + container_default_behavior: compatibility + networks_cli_compatible: false + network_mode: bridgewithdns + networks: + - name: bridgewithdns + ipv4_address: "{{ bridgewithdns.audiobookshelf }}" + env: + AUDIOBOOKSHELF_UID: "{{ audiobookshelf_user.uid }}" + AUDIOBOOKSHELF_GID: "{{ audiobookshelf_group.gid }}" + user: "{{ audiobookshelf_user.uid }}:{{ audiobookshelf_group.gid }}" + mounts: + - type: bind + source: "{{ audiobookshelf_path }}/audiobooks" + target: /audiobooks + - type: bind + source: "{{ audiobookshelf_path }}/podcasts" + target: /podcasts + - type: bind + source: "{{ audiobookshelf_path }}/config" + target: /config + - type: bind + source: "{{ audiobookshelf_path }}/metadata" + target: /metadata + tags: + - audiobookshelf-container + - docker-containers diff --git a/roles/audiobookshelf/tasks/main.yml b/roles/audiobookshelf/tasks/main.yml new file mode 100644 index 0000000..07e93a3 --- /dev/null +++ b/roles/audiobookshelf/tasks/main.yml @@ -0,0 +1,3 @@ +--- + - import_tasks: audiobookshelf.yml + tags: audiobookshelf diff --git a/roles/audiobookshelf/templates/02-audiobookshelf.conf.j2 b/roles/audiobookshelf/templates/02-audiobookshelf.conf.j2 new file mode 100644 index 0000000..0461508 --- /dev/null +++ b/roles/audiobookshelf/templates/02-audiobookshelf.conf.j2 @@ -0,0 +1,50 @@ +server { + listen 443 ssl http2; + include listen-proxy-protocol.conf; + + {% if inventory_hostname in wg_clients -%} + listen {{ wg_clients[inventory_hostname].ip }}:443 ssl http2; + {% endif -%} + + server_name {{ audiobookshelf_url }}; + + access_log /var/log/nginx/access_{{ audiobookshelf_url }}.log main; + error_log /var/log/nginx/error_{{ audiobookshelf_url }}.log warn; + + ssl_certificate /usr/local/etc/certs/{{ audiobookshelf_url }}/fullchain.pem; + ssl_certificate_key /usr/local/etc/certs/{{ audiobookshelf_url }}/privkey.pem; + + include /etc/nginx/authelia_internal.conf; + + location /feed/ { + include /etc/nginx/require_auth.conf; + + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $host; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + proxy_http_version 1.1; + + proxy_pass http://{{ bridgewithdns.audiobookshelf }}:80; + proxy_redirect http:// https://; + + proxy_hide_header "Content-Type"; + add_header "Content-Type" "application/rss+xml"; + + } + location / { + include /etc/nginx/require_auth.conf; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $host; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + proxy_http_version 1.1; + + proxy_pass http://{{ bridgewithdns.audiobookshelf }}:80; + proxy_redirect http:// https://; + } +} diff --git a/roles/hass/defaults/main.yml b/roles/hass/defaults/main.yml index ed97d53..551bba9 100644 --- a/roles/hass/defaults/main.yml +++ b/roles/hass/defaults/main.yml @@ -1 +1,2 @@ --- +blink1_enabled: false diff --git a/roles/hass/files/agp-debian-gpg.asc b/roles/hass/files/agp-debian-gpg.asc new file mode 100644 index 0000000..41903d6 --- /dev/null +++ b/roles/hass/files/agp-debian-gpg.asc @@ -0,0 +1,52 @@ +-----BEGIN PGP ARMORED FILE----- +Comment: Use "gpg --dearmor" for unpacking + +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 +=P2ir +-----END PGP ARMORED FILE----- diff --git a/roles/hass/files/agp-debian-key.asc b/roles/hass/files/agp-debian-key.asc new file mode 100644 index 0000000..41903d6 --- /dev/null +++ b/roles/hass/files/agp-debian-key.asc @@ -0,0 +1,52 @@ +-----BEGIN PGP ARMORED FILE----- +Comment: Use "gpg --dearmor" for unpacking + +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 +=P2ir +-----END PGP ARMORED FILE----- diff --git a/roles/hass/files/templates.yaml b/roles/hass/files/templates.yaml deleted file mode 100644 index 4d5f219..0000000 --- a/roles/hass/files/templates.yaml +++ /dev/null @@ -1,6 +0,0 @@ - -- sensor: - - name: "chance_of_rain" - unit_of_measurement: "%" - icon: "mdi:weather-pouring" - state: "{{ state_attr('weather.hourly', 'forecast')[:2] | map(attribute='precipitation_probability') | list | max | float }}" diff --git a/roles/hass/handlers/main.yml b/roles/hass/handlers/main.yml index 1d26331..e473eca 100644 --- a/roles/hass/handlers/main.yml +++ b/roles/hass/handlers/main.yml @@ -15,4 +15,18 @@ name: hass state: started restart: true - when: hass_container is not defined or not hass_container.changed + when: + - hass_container is not defined or not hass_container.changed + - hass_container_state|default("stopped") == "started" + +- name: restart zwavejs container + docker_container: + name: zwavejs + state: started + restart: true + when: + - zwavejs_container is not defined or not zwavejs_container.changed + - hass_container_state|default("stopped") == "started" + +- name: udevadm reload rules + command: udevadm control --reload-rules diff --git a/roles/hass/handlers/network.yml b/roles/hass/handlers/network.yml new file mode 100644 index 0000000..997e6d7 --- /dev/null +++ b/roles/hass/handlers/network.yml @@ -0,0 +1,7 @@ +--- + +- name: nmcli conn reload + command: nmcli conn reload + +- name: nmcli device wifi hotspot + command: nmcli device wifi hotspot diff --git a/roles/hass/tasks/hass.yml b/roles/hass/tasks/hass.yml index 289ad36..d0f93b9 100644 --- a/roles/hass/tasks/hass.yml +++ b/roles/hass/tasks/hass.yml @@ -1,53 +1,5 @@ --- -- name: allow ssh - ufw: - rule: allow - to_port: "22" - direction: in - state: enabled - tags: - - ufw - -- name: allow loopback - ufw: - rule: allow - interface: lo - direction: in - state: enabled - tags: - - ufw - -- name: default policy - ufw: - policy: allow - state: enabled - tags: - - ufw - -- name: deny hass cloud port stuff - ufw: - # drops packets - rule: deny - to_port: '42161' - direction: in - state: enabled - tags: - - ufw - -- name: reject zwavejs ws and hass ports (loopback only) - ufw: - # connection refused - rule: reject - to_port: "{{ item }}" - direction: in - state: enabled - with_items: - - "8091" - - "8123" - tags: - - ufw - - name: copy ssh keys for {{ hass_config_repo_name }} template: src: "private/sshkeys/{{ item }}" @@ -66,22 +18,30 @@ - name: create dir structure file: - path: "{{ systemuserlist.hass.home }}/{{ item }}" + path: "{{ systemuserlist.hass.home }}/{{ item.name }}" state: directory - mode: 0755 + mode: "{{ item.mode | default('0755') }}" owner: hass group: hass tags: - hass-dirs + loop_control: + label: "{{ item.name }}" with_items: - - home-assistant - - home-assistant/config - - home-assistant/.config - - home-assistant/media - - zwavejs - - zwavejs/app - - zwavejs/app/store - - git + - name: .local + mode: "0775" + - name: home-assistant + - name: home-assistant/config + - name: home-assistant/config/python_scripts + - name: home-assistant/config/bvg + - name: home-assistant/.config # might not be needed, misread 'cache' as 'config' + - name: home-assistant/.cache + - name: home-assistant/media + mode: "0775" + - name: zwavejs + - name: zwavejs/app + - name: zwavejs/app/store + - name: git - name: template gitconfig template: @@ -108,36 +68,38 @@ - hass-git - hass-git-clone -- name: home assistant main configuration.yaml + +- name: home assistant config files template: - src: configuration.yaml.j2 - dest: "{{ systemuserlist.hass.home }}/home-assistant/config/configuration.yaml" + src: "{{ item }}.j2" + dest: "{{ systemuserlist.hass.home }}/home-assistant/config/{{ item }}" owner: "{{ systemuserlist.hass.uid }}" group: "{{ systemuserlist.hass.gid }}" mode: 0644 notify: restart hass container + with_items: + - secrets.yaml + - configuration.yaml + - templates.yaml + - climate.yaml + - automations-ansible-managed.yaml + - scripts-ansible-managed.yaml + - blink1.yaml tags: - hass-config -- name: home assistant secrets file - template: - src: secrets.yaml.j2 - dest: "{{ systemuserlist.hass.home }}/home-assistant/config/secrets.yaml" - owner: "{{ systemuserlist.hass.uid }}" - group: "{{ systemuserlist.hass.gid }}" - mode: 0644 - notify: restart hass container - tags: - - hass-config - -- name: copy home assistant templates file +- name: copy dashboards copy: - src: templates.yaml - dest: "{{ systemuserlist.hass.home }}/home-assistant/config/templates.yaml" - owner: "{{ systemuserlist.hass.uid }}" - group: "{{ systemuserlist.hass.gid }}" - mode: 0644 + src: "private/hass/{{ item }}" + dest: "{{ systemuserlist.hass.home }}/home-assistant/config/{{ item }}" + mode: 0755 + owner: hass + group: hass notify: restart hass container + with_items: + - mini.yaml + - ui-test.yaml + - card_room.yaml tags: - hass-config @@ -163,15 +125,106 @@ - hass-cron - hass-git +- name: udev rules + template: + src: "{{ item }}.j2" + dest: /etc/udev/rules.d/{{ item }} + owner: root + group: root + mode: 0644 + notify: udevadm reload rules + with_items: + - 20-sdr.rules + tags: + - hass-udev + +# key source: +# - http://download.ag-projects.com/agp-debian-gpg.key +# - http://download.ag-projects.com/agp-debian-key.key +# gpg --enarmor roles/hass/files/agp-debian-gpg.key +# binary keys: .gpg +# ascii armor: .asc (or .key?) +- name: add apt key for sip tools + copy: + src: "{{ item }}" + dest: /etc/apt/trusted.gpg.d/{{ item }} + owner: root + group: root + mode: "0644" + with_items: + - agp-debian-gpg.asc + - agp-debian-key.asc + tags: + - packages + - hass-sip + - sip + +# - debug: +# msg: "deb [signed-by=/usr/share/keyrings/agp-debian-gpg.key] http://ag-projects.com/{{ ansible_lsb.id | lower }} {{ ansible_lsb.codename }} main" +# tags: hass-sip + +# [signed-by=/usr/share/keyrings/agp-debian-gpg.gpg] +# [signed-by=/etc/apt/trusted.gpg.d/agp-debian-gpg.asc] +- name: add repo for sip tools + apt_repository: + #repo: "{{ item }} [signed-by=/etc/apt/trusted.gpg.d/agp-debian-key.asc] http://ag-projects.com/{{ ansible_lsb.id | lower }} {{ ansible_lsb.codename }} main" + repo: "{{ item }} [signed-by=/etc/apt/trusted.gpg.d/agp-debian-key.asc] http://ag-projects.com/{{ ansible_lsb.id | lower }} sid main" + state: present + update_cache: false + with_items: + - "deb" + - "deb-src" + register: sip_repo + tags: + - packages + - hass-sip + - sip + when: false + +- name: update apt if new repo was added + apt: + update_cache: true + tags: + - packages + - hass-sip + - sip + when: + - sip_repo.changed + - false + # the host needs to have bluez installed for the container to use bluetooth -- name: install bluetooth packages +- name: install packages apt: name: + - vlc + - mplayer - bluez - bluetooth + - fapg + - podget + - sqlite3 + - rtl-433 + - mosquitto + - python3-paho-mqtt + - blink1 # git.sudo.is/ben/build-blink1 + # # sip tools + # - python3-sipsimple + # - sipclients3 state: latest tags: - packages + - hass-bluetooth + - hass-packages + - hass-sip + - sip + +- name: ensure bluetooth service is started and enabled + service: + name: bluetooth + state: started + enabled: true + tags: + - hass-bluetooth # docker run --run -it -p 8091:8091 -p 3000:3000 --network #bridgewithdns --device /dev/ttyACM0:/dev/zwave -v @@ -186,7 +239,7 @@ detach: true pull: true restart_policy: "unless-stopped" - state: "{{ container_state | default('started') }}" + state: "{{ hass_container_state | default('stopped') }}" container_default_behavior: compatibility user: "{{ systemuserlist.hass.uid }}:dialout" networks_cli_compatible: false @@ -200,18 +253,21 @@ # ws for hass<->zwavejs # hass is configured to use localhost:3000 to talk to zwavejs, but can # also use {{ bridgewithdns.zwavejs }}, but hass is very fragile and - # you have to manually work around it if it cant access zwaevjs because the - # ip/dns changed or the container moved networks. it is not configured in a - # config file either. so using localhost is the least fragile strategy. + # you have to manually work around it if it cant access zwaevjs because + # the ip/dns changed or the container moved networks. it is not + # configured in a config file either. so using localhost is the least + # fragile strategy. - "127.0.0.1:3000:3000" env: #BASE_URL: "/zwavejs/" SESSION_SECRET: "{{ zwavejs_session_secret }}" ZWAVEJS_EXTERNAL_CONFIG: /usr/src/app/store/.config-db + SERVER_SSL: "true" mounts: - type: bind source: "{{ systemuserlist.hass.home }}/zwavejs/app/store" target: /usr/src/app/store + register: zwavejs_container tags: - zwavejs - zwavejs-container @@ -219,20 +275,25 @@ - docker-containers # docker run --rm it --name hass -p 8123:8123 -e TZ=Etc/UTC -v -# /home/ben/hass:/config --network-bridgewithdns +# /home/ben/hass:/config --network=bridgewithdns # ghcr.io/home-assistant/home-assistant:stable - name: start home-assistant container docker_container: name: hass image: ghcr.io/home-assistant/home-assistant:stable + #image: git.sudo.is/ben/hass:latest detach: true pull: true restart_policy: "unless-stopped" - state: "{{ container_state | default('started') }}" + state: "{{ hass_container_state | default('stopped') }}" container_default_behavior: compatibility user: "{{ systemuserlist.hass.uid }}:{{ systemuserlist.hass.gid }}" network_mode: host + privileged: true + capabilities: + - SYS_ADMIN + - NET_ADMIN env: TZ: "Etc/UTC" mounts: @@ -242,19 +303,27 @@ - type: bind source: "{{ systemuserlist.hass.home }}/home-assistant/.config" target: /.config + - type: bind + source: "{{ systemuserlist.hass.home }}/home-assistant/.cache" + target: /.cache + - type: bind + source: "{{ systemuserlist.hass.home }}/.local" + target: /.local - type: bind source: "{{ systemuserlist.hass.home }}/home-assistant/media" target: /usr/var/media - # for bluetooth, container needs access to the dbus socket - # https://www.home-assistant.io/integrations/bluetooth/ - type: bind - source: /run/dbus/ - target: /run/dbus/ read_only: true + source: "{{ systemuserlist.archives.home }}/podgrab/data" + target: /usr/var/media/podcasts - type: bind - source: /etc/bluetooth/main.conf - target: /etc/bluetooth/main.conf - read_only: true + read_only: false + source: /run/dbus + target: /run/dbus + # - type: bind + # source: /etc/bluetooth/main.conf + # target: /etc/bluetooth/main.conf + # read_only: true # scripts from role: common # only depends on requests, which hass image has - type: bind @@ -283,20 +352,23 @@ vars: prediff_cmd: echo with_items: - - "{{ hass_url }}" + - "{{ domain }}" -- name: template nginx vhost for hass +- name: template nginx vhosts for hass and friends template: - src: 01-hass.j2 - dest: /etc/nginx/sites-enabled/01-hass + src: "01-{{ item }}.conf.j2" + dest: /etc/nginx/sites-enabled/{{ item }}.conf owner: root group: root mode: 0644 + with_items: + - hass + - zwavejs tags: - nginx - hass-nginx - zwave-nginx - notify: restart nginx + notify: reload nginx # different task because its better for the hass config to restart nginx - name: template nginx vhost for grafana-proxy diff --git a/roles/hass/tasks/network.yml b/roles/hass/tasks/network.yml new file mode 100644 index 0000000..7fb26a6 --- /dev/null +++ b/roles/hass/tasks/network.yml @@ -0,0 +1,100 @@ + +- name: allow ssh + ufw: + rule: allow + to_port: "22" + direction: in + state: enabled + tags: + - ufw + +- name: allow loopback + ufw: + rule: allow + interface: lo + direction: in + state: enabled + tags: + - ufw + +- name: default policy + ufw: + policy: allow + state: enabled + tags: + - ufw + +- name: deny hass cloud port stuff + ufw: + # drops packets + rule: deny + to_port: '42161' + direction: in + state: enabled + tags: + - ufw + +- name: reject zwavejs ws and hass ports (loopback only) + ufw: + # connection refused + rule: reject + to_port: "{{ item }}" + direction: in + state: enabled + with_items: + - "8091" + - "8123" + tags: + - ufw + +- name: get current timestamp line + command: grep "timestamp=" /etc/NetworkManager/system-connections/blackbox.connection + check_mode: false + ignore_errors: true + changed_when: false + register: timestamp + tags: + - hass-wifi + - hass-blackbox + + +# nmcli device wifi blackbox ifname wlo1 ssid {{ hass_wifi_blackbox.ssid }} password {{ hass_wifi_blackbox.pass }} +- name: config for blackbox wifi ap with NetworkManager + template: + src: blackbox.connection.j2 + dest: /etc/NetworkManager/system-connections/blackbox.connection + owner: root + group: root + mode: 0600 + tags: + - hass-wifi + - hass-blackbox + notify: + - nmcli conn reload + - nmcli device wifi hotspot + +# routing/forwarding should not be enabled, but block it to be sure +- name: allow local traffic on blackbox wifi + ufw: + rule: allow + interface: "{{ hass_wifi_blackbox.iface }}" + direction: out + dest: "{{ hass_wifi_blackbox.ip }}/{{ hass_wifi_blackbox.cidr_prefix }}" + state: enabled + tags: + - hass-wifi + - hass-blackbox + - ufw + +- name: reject everything else on the blackbox wifi + ufw: + # connection refused + rule: reject + interface: "{{ hass_wifi_blackbox.iface }}" + direction: out + dest: any + state: enabled + tags: + - hass-wifi + - hass-blackbox + - ufw diff --git a/roles/hass/templates/01-hass.conf.j2 b/roles/hass/templates/01-hass.conf.j2 new file mode 100644 index 0000000..8e7c0db --- /dev/null +++ b/roles/hass/templates/01-hass.conf.j2 @@ -0,0 +1,144 @@ +map $http_upgrade $connection_upgrade { + default upgrade; + #default $http_connection; + '' close; +} + +server { + listen 443 ssl http2; + {% if inventory_hostname in wg_clients -%} + listen {{ wg_clients[inventory_hostname].ip }}:443 ssl http2; + {% endif -%} + + include /etc/nginx/authelia_internal.conf; + include listen-proxy-protocol.conf; + include /etc/nginx/sudo-known.conf; + + server_name {{ hass_url }}; + + location / { + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + proxy_pass http://127.0.0.1:8123; + } + + # location /media { + # root {{ systemuserlist.hass.home }}/home-assistant/media; + # autoindex on; + # autoindex_exact_size off; + # } + + {% if blink1_enabled -%} + location /blink1/ { + {% for cidr in my_local_cidrs -%} + allow {{ cidr }}; + {% endfor -%} + allow {{ my_public_ips[ansible_control_host] }}/32; + allow 127.0.0.1; + deny all; + + {% if blink1_tiny_html|default(false) -%} + rewrite '^/blink1(/.*)$' $1 break; + sub_filter_once off; + sub_filter '"/' '"./'; + {% else -%} + add_header Content-Type 'application/json' always; + {% endif -%} + proxy_http_version 1.1; + proxy_pass http://localhost:{{ blink1_server_port }}; + + } + {% endif %} + location = {{ nginx_zwavejs_path }} { + # zwavejs needs to be accessed with a trailing / to respond. + # + # temporary redirects dont get remembered by the browser + # and redirect issues are no fun + return 302 https://{{ hass_url }}{{ nginx_zwavejs_path }}/; + } + location {{ nginx_zwavejs_path }} { + #add_header Access-Control-Allow-Origin "*" always; + # kill cache + add_header Last-Modified $date_gmt always; + add_header Cache-Control 'no-store' always; + if_modified_since off; + expires off; + etag off; + + # nuke the service worker cache + # sub_filter '.js' '.js?id=$request_id'; + + include /etc/nginx/require_auth.conf; + + rewrite ^ $request_uri; + rewrite '^{{ nginx_zwavejs_path }}(/.*)$' $1 break; + + proxy_set_header X-External-Path {{ nginx_zwavejs_path }}; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + #proxy_socket_keepalive on; + + ## for the special dashboard + ## https://zwave-js.github.io/zwave-js-ui/#/usage/reverse-proxy?id=using-an-http-header + ## proxy_set_header X-External-Path $http_x_ingress_path; + + proxy_pass http://{{ bridgewithdns.zwavejs }}:8091$uri; + #proxy_pass http://{{ bridgewithdns.zwavejs }}:8091/; + } + + location = {{ nginx_podgrab_path }} { + return 302 https://{{ hass_url }}{{ nginx_podgrab_path }}; + } + location {{ nginx_podgrab_path }} { + #include /etc/nginx/require_auth.conf; + + # json for a tag: + # https://hass.sudo.is/podcasts/tags/${tag} + + # add rss type or let podgrab handle it? + # slice out the url prefix + rewrite '^{{ nginx_podgrab_path }}(/.*)$' $1 break; + + # rewrite html responses to add the url prefix + sub_filter_once off; + sub_filter 'href="/' 'href="{{ nginx_podgrab_path }}/'; + sub_filter 'src="/' 'src="{{ nginx_podgrab_path }}/'; + sub_filter '= "/' '= "{{ nginx_podgrab_path }}/'; + sub_filter ':"/' ':"{{ nginx_podgrab_path }}/'; + sub_filter ':href="\'/' ':href="\'{{ nginx_podgrab_path }}/'; + sub_filter 'return "/' 'return "{{ nginx_podgrab_path }}/'; + sub_filter '("/' '("{{ nginx_podgrab_path }}/'; + sub_filter '`/' '`{{ nginx_podgrab_path }}/'; + sub_filter '/ws' '{{ nginx_podgrab_path }}/ws'; + + # nuke the service worker cache + sub_filter '.js' '.js?id=$request_id'; + sub_filter '.css' '.css?id=$request_id'; + + # headers for websockets + #proxy_set_header Upgrade $http_upgrade; + #proxy_set_header Connection $connection_upgrade; + proxy_set_header X-Real-IP $remote_addr; + #proxy_set_header X-Forwarded-Proto $scheme; + #proxy_set_header X-Forwarded-Host $http_host; + #proxy_set_header Host $http_host; + + proxy_pass http://localhost:{{ podgrab_port }}{{ nginx_podgrab_path }}/; + } + + access_log /var/log/nginx/access_{{ hass_url }}.log main; + error_log /var/log/nginx/error_{{ hass_url }}.log warn; + + ssl_session_timeout 5m; + ssl_certificate /usr/local/etc/certs/{{ domain }}/fullchain.pem; + ssl_certificate_key /usr/local/etc/certs/{{ domain }}/privkey.pem; + + fastcgi_hide_header X-Powered-By; +} diff --git a/roles/hass/templates/01-hass.j2 b/roles/hass/templates/01-hass.j2 deleted file mode 100644 index f30ada5..0000000 --- a/roles/hass/templates/01-hass.j2 +++ /dev/null @@ -1,74 +0,0 @@ -map $http_upgrade $connection_upgrade { - default upgrade; - #default $http_connection; - '' close; -} - -server { - listen 443 ssl http2; - {% if inventory_hostname in wg_clients -%} - listen {{ wg_clients[inventory_hostname].ip }}:443 ssl http2; - {% endif -%} - - include /etc/nginx/authelia_internal.conf; - include listen-proxy-protocol.conf; - include /etc/nginx/sudo-known.conf; - - server_name {{ hass_url }}; - - location / { - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; - - proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - - proxy_pass http://127.0.0.1:8123; - } - - location = {{ nginx_zwavejs_path }} { - # zwavejs needs to be accessed with a trailing / to respond. - # - # temporary redirects dont get remembered by the browser - # and redirect issues are no fun - return 302 https://{{ hass_url }}{{ nginx_zwavejs_path }}/; - } - - location {{ nginx_zwavejs_path }} { - #add_header Access-Control-Allow-Origin "*" always; - # kill cache - add_header Last-Modified $date_gmt always; - add_header Cache-Control 'no-store' always; - if_modified_since off; - expires off; - etag off; - - include /etc/nginx/require_auth.conf; - - rewrite ^ $request_uri; - rewrite '^{{ nginx_zwavejs_path }}(/.*)$' $1 break; - - proxy_set_header X-External-Path {{ nginx_zwavejs_path }}; - - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; - #proxy_socket_keepalive on; - - proxy_pass http://{{ bridgewithdns.zwavejs }}:8091$uri; - #proxy_pass http://{{ bridgewithdns.zwavejs }}:8091; - # for the special dashboard - # https://zwave-js.github.io/zwave-js-ui/#/usage/reverse-proxy?id=using-an-http-header - # proxy_set_header X-External-Path $http_x_ingress_path; - } - - access_log /var/log/nginx/access_{{ hass_url }}.log main; - error_log /var/log/nginx/error_{{ hass_url }}.log warn; - - ssl_session_timeout 5m; - ssl_certificate /usr/local/etc/certs/{{ hass_url }}/fullchain.pem; - ssl_certificate_key /usr/local/etc/certs/{{ hass_url }}/privkey.pem; - - fastcgi_hide_header X-Powered-By; -} diff --git a/roles/hass/templates/01-zwavejs.conf.j2 b/roles/hass/templates/01-zwavejs.conf.j2 new file mode 100644 index 0000000..09b6bce --- /dev/null +++ b/roles/hass/templates/01-zwavejs.conf.j2 @@ -0,0 +1,47 @@ +server { + listen 443 ssl http2; + {% if inventory_hostname in wg_clients -%} + listen {{ wg_clients[inventory_hostname].ip }}:443 ssl http2; + {% endif -%} + + include /etc/nginx/authelia_internal.conf; + include listen-proxy-protocol.conf; + include /etc/nginx/sudo-known.conf; + + server_name {{ zwavejs_url }}; + + location / { + include /etc/nginx/require_auth.conf; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # nuke cache + add_header Last-Modified $date_gmt always; + add_header Cache-Control 'no-store' always; + if_modified_since off; + expires off; + etag off; + + # nuke the service worker cache + sub_filter '.js' '.js?id=$request_id'; + ## for the special dashboard + ## https://zwave-js.github.io/zwave-js-ui/#/usage/reverse-proxy?id=using-an-http-header + #proxy_set_header X-External-Path "/"; + proxy_set_header X-External-Path $http_x_ingress_path; + + proxy_pass http://{{ bridgewithdns.zwavejs }}:8091; + } + + access_log /var/log/nginx/access_{{ zwavejs_url }}.log main; + error_log /var/log/nginx/error_{{ zwavejs_url }}.log warn; + + ssl_session_timeout 5m; + ssl_certificate /usr/local/etc/certs/{{ domain }}/fullchain.pem; + ssl_certificate_key /usr/local/etc/certs/{{ domain }}/privkey.pem; + + fastcgi_hide_header X-Powered-By; +} diff --git a/roles/hass/templates/20-sdr.rules.j2 b/roles/hass/templates/20-sdr.rules.j2 new file mode 100644 index 0000000..5d3bcf1 --- /dev/null +++ b/roles/hass/templates/20-sdr.rules.j2 @@ -0,0 +1 @@ +SUBSYSTEM=="usb", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="2838", GROUP="adm", MODE="0666", SYMLINK+="rtl_sdr" diff --git a/roles/hass/templates/automations-ansible-managed.yaml.j2 b/roles/hass/templates/automations-ansible-managed.yaml.j2 new file mode 100644 index 0000000..352282a --- /dev/null +++ b/roles/hass/templates/automations-ansible-managed.yaml.j2 @@ -0,0 +1,174 @@ +- alias: monitor_radiators_reporting + description: monitor that the battery-powered radiator knobs are working + trigger: + {% for radiator in hass_radiators -%} + {% if 'status' in radiator -%} + - platform: state + id: "radiator {{ radiator.name }}" + entity_id: + - binary_sensor.radiator_{{ radiator.name }}_reporting + to: 'off' + {% endif -%} + {% endfor %} + + condition: [] + mode: single + action: + - service: notify.persistent_notification + data: + title: 'device not reporting' + message: 'stopped reporting: {%raw%}{{ trigger.id }}{%endraw%}' + - service: notify.notify + data: + title: 'device not reporting' + message: 'stopped reporting: {%raw%}{{ trigger.id }}{%endraw%}' + +- alias: refresh_light_switches_state + description: the tkbhome switches dont automatically report their state + trigger: + - platform: time_pattern + minutes: "/1" + condition: [] + mode: single + action: + - service: zwave_js.refresh_value + data: + entity_id: + {% for item in hass_light_switches -%} + {% if item.automation_refresh|default(false) -%} + - {{ item.entity_id }} + {% endif -%} + {% endfor %} + +{% for item in hass_light_switches -%} +{% set domain = item.entity_id.split('.')[0] %} +{% set name = item.entity_id.split('.')[1] %} +{% if 'auto_off' in item %} +- alias: {{ name }}_turn_off + description: automatically turn off {{ name }} {{ domain}} + trigger: + - platform: state + entity_id: + - {{ item.entity_id }} + to: "on" + for: + minutes: {{ item.auto_off }} + condition: + - condition: state + entity_id: {{ item.entity_id }} + state: "on" + action: + - service: {{ domain }}.turn_off + data: {} + target: + entity_id: {{ item.entity_id }} + mode: single +{% endif -%} +{% endfor %} + +{% for linux_tracker in hass_linux_presence_trackers -%} +- alias: "webhook_presence_trackers_{{ linux_tracker.name }}" + description: "" + trigger: + - platform: webhook + webhook_id: {{ linux_tracker.name }}-{{ linux_tracker.webhook_key }} + id: webhook + mode: single + action: + - if: + - condition: template + value_template: >- + {%raw -%} {% {%endraw%} set current_value = states('input_text.webhook_{{ linux_tracker.name }}') {%raw%} %} {%endraw%} + {% raw %} + {{ current_value != trigger.json['state'] }} + {% endraw %} + + then: + - service: input_text.set_value + target: + entity_id: input_text.webhook_{{ linux_tracker.name }} + data: + value: "{% raw %}{{ trigger.json.state }}{% endraw %}" + +- alias: "inactive_webhook_presence_trackers_{{ linux_tracker.name }}" + description: "" + trigger: + - platform: state + entity_id: binary_sensor.{{ linux_tracker.name }}_webhook_triggering + to: "off" + action: + - service: input_text.set_value + target: + entity_id: input_text.webhook_{{ linux_tracker.name }} + data: + value: "inactive" + +{% endfor %} + +- alias: flood_sensor_washing_machine + description: "" + trigger: + - platform: state + entity_id: + - binary_sensor.flood_sensor_water_leak_detected + to: "on" + condition: [] + mode: single + action: + - service: switch.turn_off + data: {} + target: + entity_id: switch.washing_machine + - service: notify.notify + data: + title: "FLOOD SENSOR BATHROOM" + message: "WATER DETECTED! power to washing machine was cut" + - if: + - condition: zone + entity_id: person.ben + zone: zone.home + then: + - service: switch.turn_on + data: {} + target: + entity_id: switch.nad_c370 + - service: media_player.play_media + data: + media_content_type: video/webm + media_content_id: media-source://media_source/media/flood_alert.mp3 + target: + entity_id: media_player.den_tv + +- alias: buzzer_normally_closed + description: "keep the buzzer switch closed" + mode: single + trigger: + - platform: state + entity_id: + - switch.doorbell_buzzer + to: "on" + for: + hours: 0 + minutes: 0 + seconds: 1 + condition: [] + action: + - service: switch.turn_off + data: {} + target: + entity_id: switch.doorbell_buzzer + +{% for item in hass_feedreader -%} +- alias: "podcast_parse_feed_{{ item.short_name }}" + trigger: + platform: event + event_type: feedreader + event_data: + feed_url: "{{ item.url }}" + action: + service: persistent_notification.create + data: + title: "Podcast parsed" + message: {% raw %}"{{ trigger.event.data }}"{% endraw %} + +{% endfor %} diff --git a/roles/hass/templates/blackbox.connection.j2 b/roles/hass/templates/blackbox.connection.j2 new file mode 100644 index 0000000..bf78b90 --- /dev/null +++ b/roles/hass/templates/blackbox.connection.j2 @@ -0,0 +1,38 @@ +{# + # nmcli device wifi blackbox ifname wlo1 ssid {{ hass_wifi_blackbox.ssid }} password {{ hass_wifi_blackbox.pass }} + #} +[connection] +id=blackbox +uuid={{ hass_wifi_blackbox.uuid }} +type=wifi +autoconnect=false +interface-name={{ hass_wifi_blackbox.iface }} +{% if timestamp.stdout %} +{{ timestamp.stdout }} +{% else %} +timestamp={{ ansible_date_time.epoch }} +{% endif %} + +[wifi] +mode=ap +ssid={{ hass_wifi_blackbox.ssid }} +{% if hass_wifi_blackbox.hidden|default(false) -%} +hidden=true +{% endif %} + +[wifi-security] +group=ccmp; +key-mgmt=wpa-psk +pairwise=ccmp; +proto=rsn; +psk={{ hass_wifi_blackbox.pass }} + +[ipv4] +address1={{ hass_wifi_blackbox.ip }}/{{ hass_wifi_blackbox.cidr_prefix }} +method=manual + +[ipv6] +addr-gen-mode=stable-privacy +method=ignore + +[proxy] diff --git a/roles/hass/templates/blink1.yaml.j2 b/roles/hass/templates/blink1.yaml.j2 new file mode 100644 index 0000000..7c27e6b --- /dev/null +++ b/roles/hass/templates/blink1.yaml.j2 @@ -0,0 +1,71 @@ +{% if blink1_enabled -%} +friendly_name: blink1 +value_template: >- + {% raw -%} + {{ state_attr('sensor.blink1', 'rgb') != "#000000" }} + {% endraw %} + +# color_template: >- +# {% raw -%} +# {{ state_attr('sensor.blink1', 'rgb') }} +# {% endraw %} + +turn_on: + - service: rest_command.blink1_turn_on + - delay: + milliseconds: 500 + - service: homeassistant.update_entity + target: + entity_id: sensor.blink1 +turn_off: + - service: rest_command.blink1_turn_off + - delay: + milliseconds: 500 + - service: homeassistant.update_entity + target: + entity_id: sensor.blink1 +set_color: + - service: rest_command.blink1_turn_off + - service: rest_command.blink1_set_color + data: + # https://github.com/velijv/home-assistant-color-helpers#rgb-to-hex + # https://community.home-assistant.io/t/advanced-light-template-help/175654 + # https://community.home-assistant.io/t/using-hsv-hsb-to-set-colored-lights/15472 + rgb: >- + {%raw%} + {%- set h2 = h / 360 -%} + {%- set s2 = s / 100 -%} + {%- set v = 100 -%} + {%- set i = (h2 * 6 ) | round(2,'floor') | int-%} + {%- set f = h2 * 6 - i -%} + {%- set p = v * (1 - s2) -%} + {%- set q = v * (1 - f * s2) -%} + {%- set t = v * (1 - (1 - f) * s2) -%} + {%- if i % 6 == 0 -%} + {%- set r = v | int -%} + {%- set g = t | int -%} + {%- set b = p | int -%} + {%- elif i % 6 == 1 -%} + {%- set r = q | int -%} + {%- set g = v | int -%} + {%- set b = p | int -%} + {%- elif i % 6 == 2 -%} + {%- set r = p | int -%} + {%- set g = v | int -%} + {%- set b = t | int -%} + {%- elif i % 6 == 3 -%} + {%- set r = p | int -%} + {%- set g = q | int -%} + {%- set b = v | int -%} + {%- elif i % 6 == 4 -%} + {%- set r = t | int -%} + {%- set g = p | int -%} + {%- set b = v | int -%} + {%- elif i % 6 == 5 -%} + {%- set r = v | int -%} + {%- set g = p | int -%} + {%- set b = q | int -%} + {%- endif -%} + {{ '%02x%02x%02x' | format(r, g, b) }} + {%endraw%} +{% endif %} diff --git a/roles/hass/templates/climate.yaml.j2 b/roles/hass/templates/climate.yaml.j2 new file mode 100644 index 0000000..897c2b3 --- /dev/null +++ b/roles/hass/templates/climate.yaml.j2 @@ -0,0 +1,52 @@ +{% raw -%} +- platform: climate_template + name: Radiators + modes: + - "auto" + - "heat" + - "cool" + - "off" + min_temp: 0 + max_temp: 30 + + current_temperature_template: "{{ states('input_number.heating_setpoint_test') }}" + hvac_mode_template: "{{ states('input_select.heating_mode_test') }}" + current_humidity_template: 0.0 + swing_mode_template: false + availability_template: true + + set_temperature: + - service: input_number.set_value + data: + value: >- + {% set set_point = float(state_attr('climate.radiators', 'temperature'), 14.0) %} + {{ set_point }} + target: + entity_id: input_number.heating_setpoint_test + + set_hvac_mode: + - service: input_select.select_option + data: + option: >- + {% set hvac_mode = state_attr('climate.radiators', 'hvac_mode') | default('off') %} + {{ hvac_mode }} + target: + entity_id: input_select.heating_mode_test + +{% endraw %} + +{# use this in script? + {{ state_attr('climate.radiators', 'temperature') }} + + this should work, but doesnt + docs: https://www.home-assistant.io/integrations/template/ + {{ this.attributes.temperature }} + + https://github.com/jcwillox/hass-template-climate/issues/29 + + this syntax works: + {% set set_point = float(state_attr('climate.radiators', 'temperature'), 14.0) %} + {{ set_point }} + + target_temperature_template: "{{ states('input_number.heating_setpoint_test') }}" +#} diff --git a/roles/hass/templates/configuration.yaml.j2 b/roles/hass/templates/configuration.yaml.j2 index 0f12ae1..70be9ea 100644 --- a/roles/hass/templates/configuration.yaml.j2 +++ b/roles/hass/templates/configuration.yaml.j2 @@ -57,48 +57,84 @@ default_config: # zeroconf: # zone: +automation ui: !include automations.yaml +automation ansible: !include automations-ansible-managed.yaml +script: !include scripts.yaml +scene: !include scenes.yaml +template: !include templates.yaml +climate: !include climate.yaml + # Text to speech tts: - platform: voicerss api_key: !secret voicerss_api_key - platform: google_translate - -automation: !include automations.yaml -script: !include scripts.yaml -scene: !include scenes.yaml -template: !include templates.yaml - -{# calendar: - # {% for item in hass_caldav.urls %} - # - # - platform: caldav - # days: 30 - # username: !secret caldav_user - # password: !secret caldav_passwd - # # {{ item.name }} - # url: {{ item.url }} - # - # {% endfor %} - #} + - platform: picotts + language: "en-GB" calendar: + {% for item in hass_caldav.calendars -%} - platform: caldav days: 30 username: !secret caldav_user password: !secret caldav_passwd - # {{ hass_caldav.urls[0].name }} - url: {{ hass_caldav.urls[0].url }} + # {{ item.name | trim }} + url: {{ item.url | trim }} + {% endfor %} http: - # container runs with network_mode=host, so no network isolation. the docs say to not - # do this, and it doesnt work as expected either. - # using ufw/iptables for now.... - # - #server_host: 127.0.0.1 + server_host: 127.0.0.1 + server_port: 8123 trusted_proxies: - 127.0.0.1 use_x_forwarded_for: true +frontend: + themes: !include_dir_merge_named themes + +panel_custom: + - name: zwave + sidebar_title: Z-Wave + sidebar_icon: mdi:z-wave + js_url: /api/hassio/app/entrypoint.js + url_path: 'config/devices/dashboard?historyBack=1&config_entry=d6e38621854098348266029e18f93048' + embed_iframe: true + require_admin: true + config: + ingress: core_configurator + - name: automations + sidebar_title: Automations + sidebar_icon: mdi:robot + js_url: /api/hassio/app/entrypoint.js + url_path: 'config/automation/dashboard' + embed_iframe: true + require_admin: true + config: + ingress: core_configurator + - name: scripts + sidebar_title: Scripts + sidebar_icon: mdi:script + js_url: /api/hassio/app/entrypoint.js + url_path: 'config/script/dashboard' + embed_iframe: true + require_admin: true + config: + ingress: core_configurator + - name: integrations + sidebar_title: Integrations + sidebar_icon: mdi:integrated-circuit-chip + js_url: /api/hassio/app/entrypoint.js + url_path: 'config/integrations' + embed_iframe: true + require_admin: true + config: + ingress: core_configurator + + +{% set zone = {'zone': hass_zones} %} +{{ zone | to_nice_yaml }} + + homeassistant: auth_providers: - type: command_line @@ -109,6 +145,7 @@ homeassistant: currency: EUR unit_system: metric time_zone: "Europe/Berlin" + country: DE external_url: https://{{ hass_url }} internal_url: https://{{ hass_url }} allowlist_external_dirs: @@ -118,6 +155,42 @@ homeassistant: - "https://{{ hass_notflix_url }}" media_dirs: media: "/usr/var/media" + customize: + zone.home: + friendly_name: S21 + +lovelace: + mode: storage + dashboards: + lovelace-yaml: + mode: yaml + title: Mini + icon: mdi:view-dashboard + show_in_sidebar: true + require_admin: false + filename: mini.yaml + lovelace-yaml2: + mode: yaml + title: Test + icon: mdi:view-dashboard + show_in_sidebar: true + require_admin: false + filename: ui-test.yaml + + {# resources: + # - url: /config/custom_components/ui_lovelace_minimalist/cards/button-card/button-card.js + # type: module + # - url: /config/custom_components/ui_lovelace_minimalist/cards/button-card/button-card.js + # type: module #} + +recorder: + purge_keep_days: 3 + exclude: + entity_globs: + - binary_sensor.1066d799* + - sensor.1066d799* + - light.1066d799* + sensor: # https://www.home-assistant.io/integrations/dwd_weather_warnings/ @@ -144,28 +217,116 @@ sensor: # Stadt Berlin region_name: 811000000 + {% for item in hass_bvg -%} + - platform: bvg_berlin_public_transport + name: {{ item.name }} + stop_id: "{{ item.stop_id }}" + direction: {{ item.direction | trim }} + {% if item.walking_distance is defined -%} + walking_distance: {{ item.walking_distance | trim }} + {% endif -%} + file_path: "/config/bvg/" + {% endfor %} + + - platform: waqi + token: !secret waqi_token + locations: + {% for item in hass_waqi.locations -%} + - "{{ item | trim }}" + {%- endfor +%} + {#stations: + #{% for item in hass_waqi.stations -%} + #- "{{ item | trim }}" + #{% endfor %} + #} + + {% if blink1_enabled -%} + - platform: rest + resource: http://localhost:{{ blink1_server_port }}/blink1 + name: blink1 + json_attributes: + - rgb + - bright + value_template: "{%raw%}{{ value_json.rgb }}{%endraw%}" + {% endif %} + binary_sensor: - platform: workday country: DE workdays: [mon, tue, wed, thu, fri] excludes: [sat, sun, holiday] + + {% for radiator in hass_radiators -%} + {% if 'status' in radiator -%} + - platform: threshold + entity_id: sensor.radiator_{{ radiator.name }}_last_updated + # defined in templates.yaml + name: radiator_{{ radiator.name }}_reporting + # minutes + lower: 10 + {% endif -%} + {% endfor -%} {% for target in hass_ping -%} - platform: ping name: ping_{{ target.name }} host: {{ target.host }} - count: 1 - scan_interval: {{ target.interval_secs }} + count: {{ target.count | default('1') }} + scan_interval: {{ target.interval_secs | default('30') }} + {% endfor %} + +input_select: + heating_mode_test: + name: Heating Mode Test + options: + - "auto" + - "heat" + - "cool" + - "off" + initial: "off" + icon: mdi:thermometer-lines + + +input_number: + heating_setpoint_test: + name: Heating Setpoint Test + icon: mdi:home-thermometer + initial: 0 + min: 0 + max: 35 + step: 0.5 + +input_text: + {% for linux_tracker in hass_linux_presence_trackers -%} + webhook_{{ linux_tracker.name }}: + name: webhook_{{ linux_tracker.name }} + icon: "mdi:laptop" + initial: "inactive" + pattern: "^active|inactive$" {% endfor %} device_tracker: + - platform: bluetooth_le_tracker + interval_seconds: 12 + track_new_devices: false + track_battery: false + consider_home: 150 + new_device_defaults: + track_new_devices: false + - platform: bluetooth_tracker + request_rssi: false + interval_seconds: 12 + track_new_devices: false + consider_home: 150 + new_device_defaults: + track_new_devices: false - platform: ping hosts: {% for target in hass_ping -%} + {% if target.device_tracker|default(true) -%} {{ target.name }}: {{ target.host }} + {% endif -%} {% endfor %} -# enabling bluetooth -bluetooth: influxdb: host: "{{ influxdb_url }}" @@ -186,5 +347,79 @@ influxdb: source: hass home: S21 +matrix: + homeserver: https://{{ matrix_url }} + username: !secret matrix_username + password: !secret matrix_password + rooms: !secret matrix_rooms + commands: + - word: hass + name: hass + +notify: + - name: matrix + platform: matrix + default_room: !secret matrix_default_room + - name: pagerduty + platform: smtp + sender: hass@{{ domain }} + recipient: + - !secret smtp_recipient_email + server: !secret smtp_server + port: {{ smtp_port_starttls }} + username: !secret smtp_username + password: !secret smtp_passwd + encryption: starttls + sender_name: "{{ hass_url }}" + shell_command: matrixmsg: /usr/local/bin/matrixmsg.py + +rest_command: + {% if blink1_enabled -%} + blink1_turn_on: + url: {{ hass_blink1_url }}/blink1/on?bright=250 + #url: http://localhost:{{ blink1_server_port }}/blink1/fadeToRGB?rgb=ff0ff + method: GET + content_type: "application/json" + blink1_turn_off: + url: {{ hass_blink1_url }}/blink1/off + method: GET + content_type: "application/json" + blink1_turn_magenta: + url: {{ hass_blink1_url }}/blink1/fadeToRGB?rgb=ff00ff + method: GET + content_type: "application/json" + blink1_set_color: + url: "{{ hass_blink1_url }}/blink1/fadeToRGB?rgb={%raw%}{{ rgb }}{%endraw%}" + method: GET + {% endif %} + +light: + {% if blink1_enabled -%} + - platform: template + lights: + blink1: !include blink1.yaml + {% endif %} + +# enable 'wake_on_lan' for 'samsungtv' +wake_on_lan: + +samsungtv: + - host: {{ hass_wifi_blackbox.tv_ip }} + name: The TV + turn_on_action: + - service: wake_on_lan.send_magic_packet + data: + mac: "{{ hass_wifi_blackbox.tv_mac }}" + +feedreader: + urls: + {% for item in hass_feedreader -%} + - "{{ item.url | trim }}" + {% endfor %} + +{# logger: + # logs: + # pyatv: debug + # homeassistant.components.apple_tv: debug #} diff --git a/roles/hass/templates/scripts-ansible-managed.yaml.j2 b/roles/hass/templates/scripts-ansible-managed.yaml.j2 new file mode 100644 index 0000000..e69de29 diff --git a/roles/hass/templates/secrets.yaml.j2 b/roles/hass/templates/secrets.yaml.j2 index 84174ce..f20bdb9 100644 --- a/roles/hass/templates/secrets.yaml.j2 +++ b/roles/hass/templates/secrets.yaml.j2 @@ -3,6 +3,9 @@ # we can "safely" template in the actual secrets here and keep them out of # configuration.yaml, which does get synced to the repo. +openwrt_user: "{{ hass_openwrt_user }}" +openwrt_pass: "{{ hass_openwrt_pass }}" + caldav_user: "{{ hass_caldav.user }}" caldav_passwd: "{{ hass_caldav.passwd }}" @@ -10,3 +13,17 @@ voicerss_api_key: {{ voicerss_api_key }} # see group_vars/monitoring.yml influxdb_pass: {{ hass_influxdb_pass }} + +matrix_username: "{{ hass_matrix.username }}" +matrix_password: "{{ hass_matrix.password }}" +{% set rooms = {'matrix_rooms': hass_matrix.rooms} -%} +{{ rooms | to_nice_yaml }} +matrix_default_room: "{{ hass_matrix.rooms[0] }}" + +smtp_recipient_email: "{{ pagerduty_email }}" +smtp_server: "{{ smtp_server }}" +smtp_username: "{{ smtp_username }}" +smtp_passwd: "{{ smtp_passwd }}" + + +waqi_token: {{ hass_waqi.token }} diff --git a/roles/hass/templates/templates.yaml.j2 b/roles/hass/templates/templates.yaml.j2 new file mode 100644 index 0000000..b4c7f0e --- /dev/null +++ b/roles/hass/templates/templates.yaml.j2 @@ -0,0 +1,126 @@ +- sensor: + - name: "chance_of_rain" + unit_of_measurement: "%" + icon: "mdi:weather-pouring" + state: >- + {% raw -%} + {% set ipma_2h = state_attr('weather.ipma_hourly_home', 'forecast')[:2] | map(attribute='precipitation_probability') %} + {% set ipma = state_attr('weather.ipma_hourly_home', 'forecast')[0]['precipitation_probability'] | int %} + {% set owm = states('sensor.owm_home_forecast_precipitation_probability') | int %} + {{ [owm, ipma, 0] | max | int }} + {% endraw %} + + {% for radiator in hass_radiators %} + {% if 'status' in radiator %} + + - name: "radiator_{{ radiator.name }}_last_updated" + unit_of_measurement: "minutes" + icon: "mdi:update" + device_class: duration + state: >- + {% raw %} {% {% endraw -%} + set since_last_update = now() - states.{{ radiator.status }}.last_updated + {%- raw %} %} {% endraw %} + + {% raw %} {{ {% endraw -%} + since_last_update.seconds|int // 60 + {%- raw %} }} {% endraw -%} + {% endif %} + {% endfor %} + {% for linux_tracker in hass_linux_presence_trackers -%} + {% endfor %} + +- binary_sensor: + {% if blink1_enabled -%} + - name: "blink1_on" + device_class: light + state: >- + {% raw -%} + {{ state_attr('sensor.blink1', 'rgb') != "#000000" }} + {% endraw %} + {% endif %} + + - name: "heating_on" + icon: "mdi:home-thermometer" + device_class: heat + state: >- + {% raw -%} + {% set all_radiators = states.climate | selectattr("attributes", "defined") | map(attribute="attributes") | selectattr("temperature", "defined") | map(attribute="temperature") | default([]) | list %} + {% set max_radiator_temp = all_radiators | max | default(0.0) | float %} + {% set target_temp = states('input_number.target_temp_heat') | default(0.0) | float %} + {{ max_radiator_temp >= target_temp }} + {% endraw %} + + - name: s21_anyone_home + icon: "mdi:home-account" + attributes: + friendly_name: "Anyone home" + state: >- + {% raw -%} + {{ state_attr("zone.home", "persons") | default([]) | length > 0 }} + {% endraw %} + + - name: doorbell_buzzer + state: >- + {% raw %} {{ is_state("switch.doorbell_buzzer", "on") }} {% endraw +%} + icon: >- + {% raw -%} + {% if is_state("switch.doorbell_buzzer", "on") %} + mdi:electric-switch-closed + {% else %} + mdi:electric-switch + {% endif %} + {% endraw %} + + - name: washing_machine_on + icon: "mdi:washing-machine" + device_class: running + delay_on: "00:00:05" + delay_off: "00:00:05" + state: >- + {% raw -%} + {% set current = states('sensor.washing_machine_electric_a') %} + {% set power = states('sensor.washing_machine_electric_w') %} + {% if current == "unavailable" or power == "unavailable" %} + {{ false }} + {% else %} + {{ current|default(0.0)|float > 0.14 or power|default(0.0)|float > 2.8 }} + {% endif %} + {% endraw %} + + {% for linux_tracker in hass_linux_presence_trackers -%} + - name: {{ linux_tracker.name }}_active + icon: "mdi:laptop" + state: >- + {% raw -%} {% {% endraw %} set state_text = states('input_text.webhook_{{ linux_tracker.name }}') {%raw%} %} {%endraw%} + {% raw %} + {{ state_text == "active" }} + {% endraw %} + + - name: "{{ linux_tracker.name }}_webhook_triggering" + state: >- + {% raw %} {% {% endraw -%} + set since_last_triggered = now() - state_attr('automation.webhook_presence_trackers_{{ linux_tracker.name }}', 'last_triggered') + {%- raw %} %} {% endraw %} + + {% raw %} {{ {% endraw -%} + since_last_triggered.seconds|int < 666 + {%- raw %} }} {% endraw -%} + + {% endfor %} + +- button: + name: doorbell_buzzer + icon: >- + {% raw -%} + {% if is_state("switch.doorbell_buzzer", "on") %} + mdi:electric-switch-closed + {% else %} + mdi:electric-switch + {% endif %} + {% endraw +%} + press: + - service: script.toggle_switch_like_button + data: + target_switch: switch.doorbell_buzzer + press_for_ms: 200 diff --git a/roles/home/meta/main.yml b/roles/home/meta/main.yml new file mode 100644 index 0000000..ca65528 --- /dev/null +++ b/roles/home/meta/main.yml @@ -0,0 +1,7 @@ +--- + +dependencies: + - unifi + - sudoisbot + - hass + - homeaudio diff --git a/roles/homeaudio/meta/main.yml b/roles/homeaudio/meta/main.yml new file mode 100644 index 0000000..3947289 --- /dev/null +++ b/roles/homeaudio/meta/main.yml @@ -0,0 +1,7 @@ +--- + +dependencies: + - podgrab + - airconnect + - shairplay + - owntone diff --git a/roles/owntone/files/owntone-default.conf b/roles/owntone/files/owntone-default.conf new file mode 100644 index 0000000..7f54a58 --- /dev/null +++ b/roles/owntone/files/owntone-default.conf @@ -0,0 +1,452 @@ +# A quick guide to configuring OwnTone: +# +# For regular use, the most important setting to configure is "directories", +# which should be the location of your media. Whatever user you have set as +# "uid" must have read access to this location. If the location is a network +# mount, please see the README. +# +# In all likelihood, that's all you need to do! + +general { + # Username + # Make sure the user has read access to the library directories you set + # below, and full access to the databases, log and local audio + uid = "abc" + + # Database location + db_path = "/config/dbase_and_logs/songs3.db" + + # Database backup location + # Uncomment and specify a full path to enable abilty to use REST endpoint + # to initiate backup of songs3.db + #db_backup_path = "/var/cache/owntone/songs3.bak" + + # Log file and level + # Available levels: fatal, log, warning, info, debug, spam + logfile = "/config/dbase_and_logs/owntone.log" + loglevel = log + + # Admin password for the web interface + # Note that access to the web interface from computers in + # "trusted_network" (see below) does not require password + #admin_password = "" + + # Websocket port for the web interface. + websocket_port = 3688 + + # Websocket interface to bind listener to (e.g. "eth0"). Default is + # disabled, which means listen on all interfaces. + #websocket_interface = "" + + # Sets who is allowed to connect without authorisation. This applies to + # client types like Remotes, DAAP clients (iTunes) and to the web + # interface. Options are "any", "localhost" or the prefix to one or + # more ipv4/6 networks. The default is { "localhost", "192.168", "fd" } + trusted_networks = { "localhost", "192.168.21", "10.102.47", "fd" } + + # Enable/disable IPv6 + ipv6 = no + + # Set this if you want the server to bind to a specific IP address. Can + # be ipv6 or ipv4. Default (commented out or "::") is to listen on all + # IP addresses. + #bind_address = "::" + + # Location of cache database + cache_path = "/config/dbase_and_logs/cache.db" + + # DAAP requests that take longer than this threshold (in msec) get their + # replies cached for next time. Set to 0 to disable caching. + #cache_daap_threshold = 1000 + + # When starting playback, autoselect speaker (if none of the previously + # selected speakers/outputs are available) + #speaker_autoselect = no + + # Most modern systems have a high-resolution clock, but if you are on an + # unusual platform and experience audio drop-outs, you can try changing + # this option + #high_resolution_clock = yes +} + +# Library configuration +library { + # Name of the library as displayed by the clients (%h: hostname). If you + # change the name after pairing with Remote you may have to re-pair. + name = "audio.sudo.is" + + # TCP port to listen on. Default port is 3689 (daap) + port = 3689 + + # Password for the library. Optional. + #password = "" + + # Directories to index + directories = { "/music" } + + # Follow symlinks. Default: true. + #follow_symlinks = true + + # Directories containing podcasts + # For each directory that is indexed the path is matched against these + # names. If there is a match all items in the directory are marked as + # podcasts. Eg. if you index /music, and your podcasts are in + # /music/Podcasts, you can set this to "/Podcasts". + # (changing this setting only takes effect after rescan, see the README) + podcasts = { "/podcasts" } + + # Directories containing audiobooks + # For each directory that is indexed the path is matched against these + # names. If there is a match all items in the directory are marked as + # audiobooks. + # (changing this setting only takes effect after rescan, see the README) + audiobooks = { "/audiobooks" } + + # Directories containing compilations (eg soundtracks) + # For each directory that is indexed the path is matched against these + # names. If there is a match all items in the directory are marked as + # compilations. + # (changing this setting only takes effect after rescan, see the README) + compilations = { "/compilations" } + + # Compilations usually have many artists, and sometimes no album artist. + # If you dont want every artist to be listed in artist views, you can + # set a single name which will be used for all compilation tracks + # without an album artist, and for all tracks in the compilation + # directories. + # (changing this setting only takes effect after rescan, see the README) + compilation_artist = "Various Artists" + + # If your album and artist lists are cluttered, you can choose to hide + # albums and artists with only one track. The tracks will still be + # visible in other lists, e.g. songs and playlists. This setting + # currently only works in some remotes. + #hide_singles = false + + # Internet streams in your playlists will by default be shown in the + # "Radio" library, like iTunes does. However, some clients (like + # TunesRemote+) wont show the "Radio" library. If you would also like + # to have them shown like normal playlists, you can enable this option. + radio_playlists = true + + # These are the default playlists. If you want them to have other names, + # you can set it here. + #name_library = "Library" + #name_music = "Music" + #name_movies = "Movies" + #name_tvshows = "TV Shows" + #name_podcasts = "Podcasts" + #name_audiobooks = "Audiobooks" + #name_radio = "Radio" + + # Artwork file names (without file type extension) + # OwnTone will look for jpg and png files with these base names + artwork_basenames = { "artwork", "cover", "Folder" } + + # Enable searching for artwork corresponding to each individual media + # file instead of only looking for album artwork. This is disabled by + # default to reduce cache size. + #artwork_individual = false + + # File types the scanner should ignore + # Non-audio files will never be added to the database, but here you + # can prevent the scanner from even probing them. This might improve + # scan time. By default .db, .ini, .db-journal, .pdf and .metadata are + # ignored. + #filetypes_ignore = { ".db", ".ini", ".db-journal", ".pdf", ".metadata" } + + # File paths the scanner should ignore + # If you want to exclude files on a more advanced basis you can enter + # one or more POSIX regular expressions, and any file with a matching + # path will be ignored. + #filepath_ignore = { "myregex" } + + # Disable startup file scanning + # When OwnTone starts it will do an initial file scan of your + # library (and then watch it for changes). If you are sure your library + # never changes while OwnTone is not running, you can disable the + # initial file scan and save some system ressources. Disabling this scan + # may lead to OwnTones database coming out of sync with the + # library. If that happens read the instructions in the README on how + # to trigger a rescan. + #filescan_disable = false + + # Should metadata from m3u playlists, e.g. artist and title in EXTINF, + # override the metadata we get from radio streams? + #m3u_overrides = false + + # Should iTunes metadata override ours? + itunes_overrides = true + + # Should we import the content of iTunes smart playlists? + #itunes_smartpl = false + + # Decoding options for DAAP clients + # Since iTunes has native support for mpeg, mp4a, mp4v, alac and wav, + # such files will be sent as they are. Any other formats will be decoded + # to raw wav. If OwnTone detects a non-iTunes DAAP client, it is + # assumed to only support mpeg and wav, other formats will be decoded. + # Here you can change when to decode. Note that these settings have no + # effect on AirPlay. + # Formats: mp4a, mp4v, mpeg, alac, flac, mpc, ogg, wma, wmal, wmav, aif, wav + # Formats that should never be decoded + #no_decode = { "format", "format" } + # Formats that should always be decoded + #force_decode = { "format", "format" } + + # Watch named pipes in the library for data and autostart playback when + # there is data to be read. To exclude specific pipes from watching, + # consider using the above _ignore options. + #pipe_autostart = true + + # Enable automatic rating updates + # If enabled, rating is automatically updated after a song has either been + # played or skipped (only skipping to the next song is taken into account). + # The calculation is taken from the beets plugin "mpdstats" (see + # https://beets.readthedocs.io/en/latest/plugins/mpdstats.html). + # It consist of calculating a stable rating based only on the play- and + # skipcount and a rolling rating based on the current rating and the action + # (played or skipped). Both results are combined with a mix-factor of 0.75: + # new rating = 0.75 * stable rating + 0.25 * rolling rating) + #rating_updates = false + + # Allows creating, deleting and modifying m3u playlists in the library directories. + # Only supported by the player web interface and some mpd clients + # Defaults to being disabled. + allow_modifying_stored_playlists = true + + # A directory in one of the library directories that will be used as the default + # playlist directory. OwnTone creates new playlists in this directory if only + # a playlist name is provided (requires "allow_modify_stored_playlists" set to true). + default_playlist_directory = "/music/playlists" + + # By default OwnTone will - like iTunes - clear the playqueue if + # playback stops. Setting clear_queue_on_stop_disable to true will keep + # the playlist like MPD does. Note that some dacp clients do not show + # the playqueue if playback is stopped. + #clear_queue_on_stop_disable = false +} + +# Local audio output +audio { + # Name - used in the speaker list in Remote + nickname = "Computer" + + # Type of the output (alsa, pulseaudio, dummy or disabled) + #type = "alsa" + + # For pulseaudio output, an optional server hostname or IP can be + # specified (e.g. "localhost"). If not set, connection is made via local + # socket. + #server = "" + + # Audio PCM device name for local audio output - ALSA only + #card = "default" + + # Mixer channel to use for volume control - ALSA only + # If not set, PCM will be used if available, otherwise Master. + #mixer = "" + + # Mixer device to use for volume control - ALSA only + # If not set, the value for "card" will be used. + #mixer_device = "" + + # Enable or disable audio resampling to keep local audio in sync with + # e.g. Airplay. This feature relies on accurate ALSA measurements of + # delay, and some devices dont provide that. If that is the case you + # are better off disabling the feature. + #sync_disable = false + + # Here you can adjust when local audio is started relative to other + # speakers, e.g. Airplay. Negative values correspond to moving local + # audio ahead, positive correspond to delaying it. The unit is + # milliseconds. The offset must be between -1000 and 1000 (+/- 1 sec). + #offset_ms = 0 + + # To calculate what and if resampling is required, local audio delay is + # measured each second. After a period the collected measurements are + # used to estimate drift and latency, which determines if corrections + # are required. This setting sets the length of that period in seconds. + #adjust_period_seconds = 100 +} + +# ALSA device settings +# If you have multiple ALSA devices you can configure them individually via +# sections like the below. Make sure to set the "card name" correctly. See the +# README about ALSA for details. Note that these settings will override the ALSA +# settings in the "audio" section above. +#alsa "card name" { + # Name used in the speaker list. If not set, the card name will be used. + #nickname = "Computer" + + # Mixer channel to use for volume control + # If not set, PCM will be used if available, otherwise Master + #mixer = "" + + # Mixer device to use for volume control + # If not set, the card name will be used + #mixer_device = "" +# } + +# Pipe output +# Allows OwnTone to output audio data to a named pipe +#fifo { + #nickname = "fifo" + #path = "/path/to/fifo" +# } + +# AirPlay settings common to all devices +#airplay_shared { + # UDP ports used when airplay devices make connections back to + # OwnTone (choosing specific ports may be helpful when running + # OwnTone behind a firewall) + # control_port = 0 + # timing_port = 0 +# } + +# AirPlay per device settings +# (make sure you get the capitalization of the device name right) +#airplay "My AirPlay device" { + # OwnTone's volume goes to 11! If that's more than you can handle + # you can set a lower value here + #max_volume = 11 + + # Enable this option to exclude a particular AirPlay device from the + # speaker list + #exclude = false + + # Enable this option to keep a particular AirPlay device in the speaker + # list and thus ignore mdns notifications about it no longer being + # present. The speaker will remain until restart of OwnTone. + #permanent = false + + # Some devices spuriously disconnect during playback, and based on the + # device type OwnTone may attempt to reconnect. Setting this option + # overrides this so reconnecting is either always enabled or disabled. + #reconnect = false + + # AirPlay password + #password = "s1kr3t" + + # Disable AirPlay 1 (RAOP) + #raop_disable = false + + # Name used in the speaker list, overrides name from the device + #nickname = "My speaker name" +# } + +# Chromecast settings +# (make sure you get the capitalization of the device name right) +#chromecast "My Chromecast device" { + # OwnTone's volume goes to 11! If that's more than you can handle + # you can set a lower value here + #max_volume = 11 + + # Enable this option to exclude a particular device from the speaker + # list + #exclude = false + + # Name used in the speaker list, overrides name from the device + #nickname = "My speaker name" +# } + +# Spotify settings (only have effect if Spotify enabled - see README/INSTALL) +spotify { + # Set preferred bitrate for music streaming + # 0: No preference (default), 1: 96kbps, 2: 160kbps, 3: 320kbps + #bitrate = 0 + + # Your Spotify playlists will by default be put in a "Spotify" playlist + # folder. If you would rather have them together with your other + # playlists you can set this option to true. + #base_playlist_disable = false + + # Spotify playlists usually have many artist, and if you dont want + # every artist to be listed when artist browsing in Remote, you can set + # the artist_override flag to true. This will use the compilation_artist + # as album artist for Spotify items. + #artist_override = false + + # Similar to the different artists in Spotify playlists, the playlist + # items belong to different albums, and if you do not want every album + # to be listed when browsing in Remote, you can set the album_override + # flag to true. This will use the playlist name as album name for + # Spotify items. Notice that if an item is in more than one playlist, + # it will only appear in one album when browsing (in which album is + # random). + #album_override = false +} + +# RCP/Roku Soundbridge output settings +# (make sure you get the capitalization of the device name right) +#rcp "My SoundBridge device" { + # Enable this option to exclude a particular device from the speaker + # list + #exclude = false + + # A Roku/SoundBridge can power up in 2 modes: (default) reconnect to the + # previously used library (ie OwnTone) or in a 'cleared library' mode. + # The Roku power up behaviour is affected by how OwnTone disconnects + # from the Roku device. + # + # Set to false to maintain default Roku power on behaviour + #clear_on_close = false +# } + + +# MPD configuration (only have effect if MPD enabled - see README/INSTALL) +mpd { + # TCP port to listen on for MPD client requests. + # Default port is 6600, set to 0 to disable MPD support. + #port = 6600 + + # HTTP port to listen for artwork requests (only supported by some MPD + # clients and will need additional configuration in the MPD client to + # work). Set to 0 to disable serving artwork over http. + #http_port = 0 +} + +# SQLite configuration (allows to modify the operation of the SQLite databases) +# Make sure to read the SQLite documentation for the corresponding PRAGMA +# statements as changing them from the defaults may increase the possibility of +# database corruptions! By default the SQLite default values are used. +sqlite { + # Cache size in number of db pages for the library database + # (SQLite default page size is 1024 bytes and cache size is 2000 pages) + #pragma_cache_size_library = 2000 + + # Cache size in number of db pages for the daap cache database + # (SQLite default page size is 1024 bytes and cache size is 2000 pages) + #pragma_cache_size_cache = 2000 + + # Sets the journal mode for the database + # DELETE (default), TRUNCATE, PERSIST, MEMORY, WAL, OFF + #pragma_journal_mode = DELETE + + # Change the setting of the "synchronous" flag + # 0: OFF, 1: NORMAL, 2: FULL (default) + #pragma_synchronous = 2 + + # Number of bytes set aside for memory-mapped I/O for the library database + # (requires sqlite 3.7.17 or later) + # 0: disables mmap (default), any other value > 0: number of bytes for mmap + #pragma_mmap_size_library = 0 + + # Number of bytes set aside for memory-mapped I/O for the cache database + # (requires sqlite 3.7.17 or later) + # 0: disables mmap (default), any other value > 0: number of bytes for mmap + #pragma_mmap_size_cache = 0 + + # Should the database be vacuumed on startup? (increases startup time, + # but may reduce database size). Default is yes. + #vacuum = yes +} + +# Streaming audio settings for remote connections (ie stream.mp3) +streaming { + # Sample rate, typically 44100 or 48000 + #sample_rate = 44100 + + # Set the MP3 streaming bit rate (in kbps), valid options: 64 / 96 / 128 / 192 / 320 + bit_rate = 320 +} diff --git a/roles/owntone/handlers/main.yml b/roles/owntone/handlers/main.yml new file mode 100644 index 0000000..34e6178 --- /dev/null +++ b/roles/owntone/handlers/main.yml @@ -0,0 +1,12 @@ +--- + +- name: reload nginx + service: + name: nginx + state: reloaded + +- name: restart owntone container + docker_container: + name: hass + state: started + restart: true diff --git a/roles/owntone/tasks/main.yml b/roles/owntone/tasks/main.yml new file mode 100644 index 0000000..87b689c --- /dev/null +++ b/roles/owntone/tasks/main.yml @@ -0,0 +1,4 @@ +--- + +- import_tasks: owntone.yml + tags: owntone diff --git a/roles/owntone/tasks/owntone.yml b/roles/owntone/tasks/owntone.yml new file mode 100644 index 0000000..3bc8cb5 --- /dev/null +++ b/roles/owntone/tasks/owntone.yml @@ -0,0 +1,145 @@ +--- + +- name: create dir structure + file: + state: directory + path: "{{ owntone_path }}/{{ item.name }}" + mode: "{{ item.mode | default('0770') }}" + owner: "{{ owntone_user.uid }}" + group: "{{ owntone_group.gid }}" + tags: + - owntone-dirs + loop_control: + label: "{{ item.name }}" + with_items: + - name: '' + - name: config + - name: log + - name: audio + - name: audio/local_music + - name: audio/playlists + - name: audio/compilations + - name: audio/pipes + #- name: audio/lidarr + #- name: audio/audiobooks + #- name: audio/podcasts + +- name: create input pipe + command: + cmd: mkfifo "{{ owntone_path }}/audio/pipes/{{ item }}" + creates: "{{ owntone_path }}/audio/pipes/{{ item }}" + become_user: "{{ owntone_user.username }}" + loop_control: + label: "{{ item }}" + with_items: + - shairport-output.fifo + - shairport-metadata.fifo + tags: + - input.fifo + +- name: install certs + copy: + src: "/usr/local/etc/letsencrypt/live/{{ item }}" + dest: "/usr/local/etc/certs/" + owner: root + group: root + mode: 0755 + tags: + - letsencrypt-certs + notify: reload nginx + vars: + prediff_cmd: echo + with_items: + - "{{ domain }}" + +- name: template nginx vhost + template: + src: 01-owntone.conf.j2 + dest: /etc/nginx/sites-enabled/01-owntone.conf + owner: root + group: root + mode: 0644 + tags: + - nginx + - owntone-nginx + notify: reload nginx + +- name: template config file + template: + src: owntone.conf.j2 + dest: "{{ owntone_path }}/config/owntone.conf" + owner: "{{ owntone_user.uid }}" + group: "{{ owntone_group.gid }}" + mode: 0644 + notify: + - restart owntone container + tags: + - owntone.conf + +- name: cron file + template: + src: owntone-cron.j2 + dest: /etc/cron.d/owntone + owner: root + group: root + mode: 0600 + tags: + - cron + - owntone-cron + +- name: install utils for tagging + apt: + name: + + - id3v2 + - ffmpeg + state: present + tags: + - packages + +- name: install yt-dlp + pip: + name: yt-dlp + state: latest + tags: + - packages + - pip-packages + - yt-dlp + +- name: fuse allow other + lineinfile: + path: /etc/fuse.conf + line: user_allow_other + state: present + +- name: start owntone container + docker_container: + name: owntone + #image: lscr.io/linuxserver/daapd:latest + image: git.sudo.is/ben/owntone:latest + detach: true + pull: true + restart_policy: "unless-stopped" + state: started + container_default_behavior: compatibility + networks_cli_compatible: false + # user: + network_mode: host + env: + VITE_OWNTONE_URL: "https://{{ owntone_url }}" + mounts: + - type: bind + source: "{{ owntone_path }}/config/owntone.conf" + target: "/etc/owntone.conf" + - type: bind + source: "{{ owntone_path }}/config" + target: "/config" + - type: bind + source: "{{ owntone_path }}/log" + target: "/log" + - type: bind + source: "{{ owntone_path }}/audio" + target: "/audio" + tags: + - owntone-container + - docker-containers diff --git a/roles/owntone/templates/01-owntone.conf.j2 b/roles/owntone/templates/01-owntone.conf.j2 new file mode 100644 index 0000000..559df50 --- /dev/null +++ b/roles/owntone/templates/01-owntone.conf.j2 @@ -0,0 +1,70 @@ +server { + listen 443 ssl http2; + # listen {{ owntone_port_tcp }}; + + {% if inventory_hostname in wg_clients -%} + listen {{ wg_clients[inventory_hostname].ip }}:443 ssl http2; + {% endif -%} + + include /etc/nginx/authelia_internal.conf; + include listen-proxy-protocol.conf; + include /etc/nginx/sudo-known.conf; + + server_name {{ owntone_url }}; + + access_log /var/log/nginx/access_{{ owntone_url }}.log main; + error_log /var/log/nginx/error_{{ owntone_url }}.log warn; + + ssl_certificate /usr/local/etc/certs/{{ domain }}/fullchain.pem; + ssl_certificate_key /usr/local/etc/certs/{{ domain }}/privkey.pem; + + # ! + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers off; + + location / { + sub_filter 'owntone.local:3689' '{{ owntone_url }}'; + sub_filter 'http%3A%2F%2Fowntone.local%3A3689' 'https%3A%2F%2F{{ owntone_url }}'; + sub_filter 'http://owntone.local:3689' 'https://{{ owntone_url }}'; + sub_filter_types '*'; + sub_filter_once off; + + proxy_pass http://127.0.0.1:{{ owntone_port_tcp }}/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +} +server { + # only needs to be accessed by players that are being streamed to + # maybe need to remove 'http2' + listen {{ ansible_default_ipv4.address }}:{{ owntone_port_ws }} ssl http2; + + server_name {{ owntone_url }}; + + access_log /var/log/nginx/access_{{ owntone_url }}_{{ owntone_port_ws }}.log main; + error_log /var/log/nginx/error_{{ owntone_url }}_{{ owntone_port_ws }}.log warn; + + ssl_certificate /usr/local/etc/certs/{{ domain }}/fullchain.pem; + ssl_certificate_key /usr/local/etc/certs/{{ domain }}/privkey.pem; + + # ! + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers off; + + location / { + proxy_pass http://127.0.0.1:{{ owntone_port_ws }}/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + +} diff --git a/roles/owntone/templates/owntone-cron.j2 b/roles/owntone/templates/owntone-cron.j2 new file mode 100644 index 0000000..b79dde9 --- /dev/null +++ b/roles/owntone/templates/owntone-cron.j2 @@ -0,0 +1,7 @@ +# {{ ansible_managed }} + +# m h dom mon dow + +*/60 * * * * {{ owntone_user.username }} touch {{ owntone_path }}/audio/local_music/trigger.init-rescan + +# diff --git a/roles/owntone/templates/owntone.conf.j2 b/roles/owntone/templates/owntone.conf.j2 new file mode 100644 index 0000000..8e0214b --- /dev/null +++ b/roles/owntone/templates/owntone.conf.j2 @@ -0,0 +1,472 @@ +# A quick guide to configuring OwnTone: +# +# For regular use, the most important setting to configure is "directories", +# which should be the location of your media. Whatever user you have set as +# "uid" must have read access to this location. If the location is a network +# mount, please see the README. +# +# In all likelihood, that's all you need to do! + +general { + # Username + # Make sure the user has read access to the library directories you set + # below, and full access to the databases, log and local audio + uid = "owntone" + + db_path = "/config/dbase_and_logs/songs3.db" + + # Database backup location + # Uncomment and specify a full path to enable abilty to use REST endpoint + # to initiate backup of songs3.db + #db_backup_path = "/var/cache/owntone/songs3.bak" + + # Log file and level + # Available levels: fatal, log, warning, info, debug, spam + logfile = "/log/owntone.log" + loglevel = log + + # Admin password for the web interface + # Note that access to the web interface from computers in + # "trusted_network" (see below) does not require password + admin_password = "{{ owntone_admin_pass }}" + + # Websocket port for the web interface. + websocket_port = {{ owntone_port_ws }} + + # Websocket interface to bind listener to (e.g. "eth0"). Default is + # disabled, which means listen on all interfaces. + websocket_interface = "{{ owntone_interface }}" + + # Sets who is allowed to connect without authorisation. This applies to + # client types like Remotes, DAAP clients (iTunes) and to the web + # interface. Options are "any", "localhost" or the prefix to one or + # more ipv4/6 networks. The default is { "localhost", "192.168", "fd" } + {% set s21 = s21_cidr.split(".")[:3] | join(".") -%} + trusted_networks = { "localhost", "{{ s21 }}", "fd" } + + # Enable/disable IPv6 + ipv6 = no + + # Set this if you want the server to bind to a specific IP address. Can + # be ipv6 or ipv4. Default (commented out or "::") is to listen on all + # IP addresses. + #bind_address = "{{ owntone_interface }}" + + # Location of cache database + cache_path = "/config/dbase_and_logs/cache.db" + + # DAAP requests that take longer than this threshold (in msec) get their + # replies cached for next time. Set to 0 to disable caching. + #cache_daap_threshold = 1000 + + # When starting playback, autoselect speaker (if none of the previously + # selected speakers/outputs are available) + #speaker_autoselect = no + + # Most modern systems have a high-resolution clock, but if you are on an + # unusual platform and experience audio drop-outs, you can try changing + # this option + #high_resolution_clock = yes +} + +# Library configuration +library { + # Name of the library as displayed by the clients (%h: hostname). If you + # change the name after pairing with Remote you may have to re-pair. + name = "{{ owntone_url }}" + + # TCP port to listen on. Default port is 3689 (daap) + port = {{ owntone_port_tcp }} + + # Password for the library. Optional. + #password = "{{ owntone_library_pass }}" + + # Directories to index + directories = { "/audio" } + + # Follow symlinks. Default: true. + #follow_symlinks = true + + # Directories containing podcasts + # For each directory that is indexed the path is matched against these + # names. If there is a match all items in the directory are marked as + # podcasts. Eg. if you index /music, and your podcasts are in + # /music/Podcasts, you can set this to "/Podcasts". + # (changing this setting only takes effect after rescan, see the README) + podcasts = { "/podcasts" } + + # Directories containing audiobooks + # For each directory that is indexed the path is matched against these + # names. If there is a match all items in the directory are marked as + # audiobooks. + # (changing this setting only takes effect after rescan, see the README) + audiobooks = { "/audiobooks" } + + # Directories containing compilations (eg soundtracks) + # For each directory that is indexed the path is matched against these + # names. If there is a match all items in the directory are marked as + # compilations. + # (changing this setting only takes effect after rescan, see the README) + compilations = { "/compilations" } + + # Compilations usually have many artists, and sometimes no album artist. + # If you dont want every artist to be listed in artist views, you can + # set a single name which will be used for all compilation tracks + # without an album artist, and for all tracks in the compilation + # directories. + # (changing this setting only takes effect after rescan, see the README) + compilation_artist = "Various Artists" + + # If your album and artist lists are cluttered, you can choose to hide + # albums and artists with only one track. The tracks will still be + # visible in other lists, e.g. songs and playlists. This setting + # currently only works in some remotes. + #hide_singles = false + + # Internet streams in your playlists will by default be shown in the + # "Radio" library, like iTunes does. However, some clients (like + # TunesRemote+) wont show the "Radio" library. If you would also like + # to have them shown like normal playlists, you can enable this option. + radio_playlists = false + + # These are the default playlists. If you want them to have other names, + # you can set it here. + #name_library = "Library" + #name_music = "Music" + #name_movies = "Movies" + #name_tvshows = "TV Shows" + #name_podcasts = "Podcasts" + #name_audiobooks = "Audiobooks" + #name_radio = "Radio" + + # Artwork file names (without file type extension) + # OwnTone will look for jpg and png files with these base names + artwork_basenames = { "artwork", "cover", "Folder" } + + # Enable searching for artwork corresponding to each individual media + # file instead of only looking for album artwork. This is disabled by + # default to reduce cache size. + artwork_individual = true + + # File types the scanner should ignore + # Non-audio files will never be added to the database, but here you + # can prevent the scanner from even probing them. This might improve + # scan time. By default .db, .ini, .db-journal, .pdf and .metadata are + # ignored. + #filetypes_ignore = { ".db", ".ini", ".db-journal", ".pdf", ".metadata" } + + # File paths the scanner should ignore + # If you want to exclude files on a more advanced basis you can enter + # one or more POSIX regular expressions, and any file with a matching + # path will be ignored. + #filepath_ignore = { "myregex" } + + # Disable startup file scanning + # When OwnTone starts it will do an initial file scan of your + # library (and then watch it for changes). If you are sure your library + # never changes while OwnTone is not running, you can disable the + # initial file scan and save some system ressources. Disabling this scan + # may lead to OwnTones database coming out of sync with the + # library. If that happens read the instructions in the README on how + # to trigger a rescan. + #filescan_disable = false + + # Should metadata from m3u playlists, e.g. artist and title in EXTINF, + # override the metadata we get from radio streams? + #m3u_overrides = false + + # Should iTunes metadata override ours? + itunes_overrides = {{ owntone_itunes_metadata_override | default(false) }} + + # Should we import the content of iTunes smart playlists? + #itunes_smartpl = false + + # Decoding options for DAAP clients + # Since iTunes has native support for mpeg, mp4a, mp4v, alac and wav, + # such files will be sent as they are. Any other formats will be decoded + # to raw wav. If OwnTone detects a non-iTunes DAAP client, it is + # assumed to only support mpeg and wav, other formats will be decoded. + # Here you can change when to decode. Note that these settings have no + # effect on AirPlay. + # Formats: mp4a, mp4v, mpeg, alac, flac, mpc, ogg, wma, wmal, wmav, aif, wav + # Formats that should never be decoded + #no_decode = { "format", "format" } + # Formats that should always be decoded + #force_decode = { "format", "format" } + + # Watch named pipes in the library for data and autostart playback when + # there is data to be read. To exclude specific pipes from watching, + # consider using the above _ignore options. + pipe_autostart = true + + # Enable automatic rating updates + # If enabled, rating is automatically updated after a song has either been + # played or skipped (only skipping to the next song is taken into account). + # The calculation is taken from the beets plugin "mpdstats" (see + # https://beets.readthedocs.io/en/latest/plugins/mpdstats.html). + # It consist of calculating a stable rating based only on the play- and + # skipcount and a rolling rating based on the current rating and the action + # (played or skipped). Both results are combined with a mix-factor of 0.75: + # new rating = 0.75 * stable rating + 0.25 * rolling rating) + #rating_updates = false + + # Allows creating, deleting and modifying m3u playlists in the library directories. + # Only supported by the player web interface and some mpd clients + # Defaults to being disabled. + allow_modifying_stored_playlists = true + + # A directory in one of the library directories that will be used as the default + # playlist directory. OwnTone creates new playlists in this directory if only + # a playlist name is provided (requires "allow_modify_stored_playlists" set to true). + default_playlist_directory = "/audio/playlists" + + # By default OwnTone will - like iTunes - clear the playqueue if + # playback stops. Setting clear_queue_on_stop_disable to true will keep + # the playlist like MPD does. Note that some dacp clients do not show + # the playqueue if playback is stopped. + clear_queue_on_stop_disable = true +} + +# Local audio output +audio { + # Name - used in the speaker list in Remote + nickname = "Computer" + + # Type of the output (alsa, pulseaudio, dummy or disabled) + type = "disabled" + + # For pulseaudio output, an optional server hostname or IP can be + # specified (e.g. "localhost"). If not set, connection is made via local + # socket. + #server = "" + + # Audio PCM device name for local audio output - ALSA only + #card = "default" + + # Mixer channel to use for volume control - ALSA only + # If not set, PCM will be used if available, otherwise Master. + #mixer = "" + + # Mixer device to use for volume control - ALSA only + # If not set, the value for "card" will be used. + #mixer_device = "" + + # Enable or disable audio resampling to keep local audio in sync with + # e.g. Airplay. This feature relies on accurate ALSA measurements of + # delay, and some devices dont provide that. If that is the case you + # are better off disabling the feature. + #sync_disable = false + + # Here you can adjust when local audio is started relative to other + # speakers, e.g. Airplay. Negative values correspond to moving local + # audio ahead, positive correspond to delaying it. The unit is + # milliseconds. The offset must be between -1000 and 1000 (+/- 1 sec). + #offset_ms = 0 + + # To calculate what and if resampling is required, local audio delay is + # measured each second. After a period the collected measurements are + # used to estimate drift and latency, which determines if corrections + # are required. This setting sets the length of that period in seconds. + #adjust_period_seconds = 100 +} + +# ALSA device settings +# If you have multiple ALSA devices you can configure them individually via +# sections like the below. Make sure to set the "card name" correctly. See the +# README about ALSA for details. Note that these settings will override the ALSA +# settings in the "audio" section above. +#alsa "card name" { + # Name used in the speaker list. If not set, the card name will be used. + #nickname = "Computer" + + # Mixer channel to use for volume control + # If not set, PCM will be used if available, otherwise Master + #mixer = "" + + # Mixer device to use for volume control + # If not set, the card name will be used + #mixer_device = "" +# } + +# Pipe output +# Allows OwnTone to output audio data to a named pipe +fifo { + nickname = "fifo" + path = "/audio/output.fifo" +} + +# AirPlay settings common to all devices +#airplay_shared { + # UDP ports used when airplay devices make connections back to + # OwnTone (choosing specific ports may be helpful when running + # OwnTone behind a firewall) + # control_port = 0 + # timing_port = 0 +# } + +# AirPlay per device settings +# (make sure you get the capitalization of the device name right) +#airplay "My AirPlay device" { + # OwnTone's volume goes to 11! If that's more than you can handle + # you can set a lower value here + #max_volume = 11 + + # Enable this option to exclude a particular AirPlay device from the + # speaker list + #exclude = false + + # Enable this option to keep a particular AirPlay device in the speaker + # list and thus ignore mdns notifications about it no longer being + # present. The speaker will remain until restart of OwnTone. + #permanent = false + + # Some devices spuriously disconnect during playback, and based on the + # device type OwnTone may attempt to reconnect. Setting this option + # overrides this so reconnecting is either always enabled or disabled. + #reconnect = false + + # AirPlay password + #password = "s1kr3t" + + # Disable AirPlay 1 (RAOP) + #raop_disable = false + + # Name used in the speaker list, overrides name from the device + #nickname = "My speaker name" +# } + +{% for item in owntone_airplay -%} +{% if item.exclude|default(false) -%} +# Excluded AirPlay device: {{ item.name }} +{% endif %} +airplay "{{ item.name }}" { + {% if 'nickname' in item -%} + nickname = "{{ item.nickname }}" + {% endif -%} + {% if 'password' in item -%} + password = "{{ item.password }}" + {% endif -%} + {% if 'max_volume' in item -%} + max_volume = {{ item.max_volume }} + {% endif -%} + exclude = {{ item.exclude|default(false) | lower }} + permanent = {{ item.permanent|default(false) | lower }} + reconnect = {{ item.reconnect|default(false) | lower }} +} +{% endfor %} + +# Chromecast settings +# (make sure you get the capitalization of the device name right) +#chromecast "My Chromecast device" { + # OwnTone's volume goes to 11! If that's more than you can handle + # you can set a lower value here + #max_volume = 11 + + # Enable this option to exclude a particular device from the speaker + # list + #exclude = false + + # Name used in the speaker list, overrides name from the device + #nickname = "My speaker name" +# } + +# Spotify settings (only have effect if Spotify enabled - see README/INSTALL) +spotify { + # Set preferred bitrate for music streaming + # 0: No preference (default), 1: 96kbps, 2: 160kbps, 3: 320kbps + bitrate = 3 + + # Your Spotify playlists will by default be put in a "Spotify" playlist + # folder. If you would rather have them together with your other + # playlists you can set this option to true. + base_playlist_disable = true + + # Spotify playlists usually have many artist, and if you dont want + # every artist to be listed when artist browsing in Remote, you can set + # the artist_override flag to true. This will use the compilation_artist + # as album artist for Spotify items. + artist_override = true + + # Similar to the different artists in Spotify playlists, the playlist + # items belong to different albums, and if you do not want every album + # to be listed when browsing in Remote, you can set the album_override + # flag to true. This will use the playlist name as album name for + # Spotify items. Notice that if an item is in more than one playlist, + # it will only appear in one album when browsing (in which album is + # random). + album_override = true +} + +# RCP/Roku Soundbridge output settings +# (make sure you get the capitalization of the device name right) +#rcp "My SoundBridge device" { + # Enable this option to exclude a particular device from the speaker + # list + #exclude = false + + # A Roku/SoundBridge can power up in 2 modes: (default) reconnect to the + # previously used library (ie OwnTone) or in a 'cleared library' mode. + # The Roku power up behaviour is affected by how OwnTone disconnects + # from the Roku device. + # + # Set to false to maintain default Roku power on behaviour + #clear_on_close = false +# } + + +# MPD configuration (only have effect if MPD enabled - see README/INSTALL) +mpd { + # TCP port to listen on for MPD client requests. + # Default port is 6600, set to 0 to disable MPD support. + port = {{ owntone_port_mpd|default('6600') }} + + # HTTP port to listen for artwork requests (only supported by some MPD + # clients and will need additional configuration in the MPD client to + # work). Set to 0 to disable serving artwork over http. + #http_port = 0 +} + +# SQLite configuration (allows to modify the operation of the SQLite databases) +# Make sure to read the SQLite documentation for the corresponding PRAGMA +# statements as changing them from the defaults may increase the possibility of +# database corruptions! By default the SQLite default values are used. +sqlite { + # Cache size in number of db pages for the library database + # (SQLite default page size is 1024 bytes and cache size is 2000 pages) + #pragma_cache_size_library = 2000 + + # Cache size in number of db pages for the daap cache database + # (SQLite default page size is 1024 bytes and cache size is 2000 pages) + #pragma_cache_size_cache = 2000 + + # Sets the journal mode for the database + # DELETE (default), TRUNCATE, PERSIST, MEMORY, WAL, OFF + #pragma_journal_mode = DELETE + + # Change the setting of the "synchronous" flag + # 0: OFF, 1: NORMAL, 2: FULL (default) + #pragma_synchronous = 2 + + # Number of bytes set aside for memory-mapped I/O for the library database + # (requires sqlite 3.7.17 or later) + # 0: disables mmap (default), any other value > 0: number of bytes for mmap + #pragma_mmap_size_library = 0 + + # Number of bytes set aside for memory-mapped I/O for the cache database + # (requires sqlite 3.7.17 or later) + # 0: disables mmap (default), any other value > 0: number of bytes for mmap + #pragma_mmap_size_cache = 0 + + # Should the database be vacuumed on startup? (increases startup time, + # but may reduce database size). Default is yes. + #vacuum = yes +} + +# Streaming audio settings for remote connections (ie stream.mp3) +streaming { + # Sample rate, typically 44100 or 48000 + #sample_rate = 44100 + + # Set the MP3 streaming bit rate (in kbps), valid options: 64 / 96 / 128 / 192 / 320 + bit_rate = 320 +} diff --git a/roles/sensnet/meta/main.yml b/roles/sensnet/meta/main.yml index 0677e74..ed97d53 100644 --- a/roles/sensnet/meta/main.yml +++ b/roles/sensnet/meta/main.yml @@ -1,7 +1 @@ --- - -dependencies: - - mariadb - - zflux - - sudoisbot - - hass diff --git a/roles/sensors/tasks/sensors.yml b/roles/sensors/tasks/sensors.yml index 646b37b..1296512 100644 --- a/roles/sensors/tasks/sensors.yml +++ b/roles/sensors/tasks/sensors.yml @@ -56,6 +56,191 @@ when: dht_builder|default(False) tags: dht + # https://bluedot.readthedocs.io/en/latest/pairpipi.html#using-the-command-line + # + # $ sudo bluetoothctl + # [bluetooth]# discoverable on + # Changing discoverable on succeeded + # [bluetooth]# pairable on + # Changing pairable on succeeded + # [bluetooth]# agent on + # Agent is already registered + # [bluetooth]# default-agent + # Default agent request successful + # [bluetooth]# scan on + # [bluetooth]# pair EA:26:7D:4A:ED:E4 + # 0a-11e5-a837-0800200c9a66 + # Vendor specific + # [NEW] Descriptor + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0009/char000d/desc000f + # 00002901-0000-1000-8000-00805f9b34fb + # Characteristic User Description + # [NEW] Descriptor + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0009/char000d/desc0010 + # 00002902-0000-1000-8000-00805f9b34fb + # Client Characteristic Configuration + # [NEW] Characteristic + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0009/char0011 + # e6807d23-b90a-11e5-a837-0800200c9a66 + # Vendor specific + # [NEW] Descriptor + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0009/char0011/desc0013 + # 00002901-0000-1000-8000-00805f9b34fb + # Characteristic User Description + # [NEW] Characteristic + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0009/char0014 + # e6807d24-b90a-11e5-a837-0800200c9a66 + # Vendor specific + # [NEW] Descriptor + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0009/char0014/desc0016 + # 00002901-0000-1000-8000-00805f9b34fb + # Characteristic User Description + # [NEW] Descriptor + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0009/char0014/desc0017 + # 00002902-0000-1000-8000-00805f9b34fb + # Client Characteristic Configuration + # [NEW] Characteristic + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0009/char0018 + # e6807d25-b90a-11e5-a837-0800200c9a66 + # Vendor specific + # [NEW] Descriptor + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0009/char0018/desc001a + # 00002901-0000-1000-8000-00805f9b34fb + # Characteristic User Description + # [NEW] Characteristic + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0009/char001b + # e6807d26-b90a-11e5-a837-0800200c9a66 + # Vendor specific + # [NEW] Descriptor + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0009/char001b/desc001d + # 00002901-0000-1000-8000-00805f9b34fb + # Characteristic User Description + # [NEW] Characteristic + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0009/char001e + # e6807d27-b90a-11e5-a837-0800200c9a66 + # Vendor specific + # [NEW] Descriptor + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0009/char001e/desc0020 + # 00002901-0000-1000-8000-00805f9b34fb + # Characteristic User Description + # [NEW] Characteristic + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0009/char0021 + # e6807e20-b90a-11e5-a837-0800200c9a66 + # Vendor specific + # [NEW] Descriptor + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0009/char0021/desc0023 + # 00002901-0000-1000-8000-00805f9b34fb + # Characteristic User Description + # [NEW] Descriptor + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0009/char0021/desc0024 + # 00002902-0000-1000-8000-00805f9b34fb + # Client Characteristic Configuration + # [NEW] Characteristic + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0009/char0025 + # e6807e21-b90a-11e5-a837-0800200c9a66 + # Vendor specific + # [NEW] Descriptor + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0009/char0025/desc0027 + # 00002901-0000-1000-8000-00805f9b34fb + # Characteristic User Description + # [NEW] Characteristic + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0009/char0028 + # e6807e24-b90a-11e5-a837-0800200c9a66 + # Vendor specific + # [NEW] Descriptor + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0009/char0028/desc002a + # 00002901-0000-1000-8000-00805f9b34fb + # Characteristic User Description + # [NEW] Descriptor + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0009/char0028/desc002b + # 00002902-0000-1000-8000-00805f9b34fb + # Client Characteristic Configuration + # [NEW] Characteristic + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0009/char002c + # e6807e25-b90a-11e5-a837-0800200c9a66 + # Vendor specific + # [NEW] Descriptor + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0009/char002c/desc002e + # 00002901-0000-1000-8000-00805f9b34fb + # Characteristic User Description + # [NEW] Primary Service + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service002f + # 0000181a-0000-1000-8000-00805f9b34fb + # Environmental Sensing + # [NEW] Characteristic + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service002f/char0030 + # 00002a6e-0000-1000-8000-00805f9b34fb + # Temperature + # [NEW] Descriptor + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service002f/char0030/desc0032 + # 00002901-0000-1000-8000-00805f9b34fb + # Characteristic User Description + # [NEW] Descriptor + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service002f/char0030/desc0033 + # 00002902-0000-1000-8000-00805f9b34fb + # Client Characteristic Configuration + # [NEW] Characteristic + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service002f/char0034 + # 00002a6f-0000-1000-8000-00805f9b34fb + # Humidity + # [NEW] Descriptor + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service002f/char0034/desc0036 + # 00002901-0000-1000-8000-00805f9b34fb + # Characteristic User Description + # [NEW] Descriptor + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service002f/char0034/desc0037 + # 00002902-0000-1000-8000-00805f9b34fb + # Client Characteristic Configuration + # [NEW] Primary Service + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0038 + # 0000180a-0000-1000-8000-00805f9b34fb + # Device Information + # [NEW] Characteristic + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0038/char0039 + # 00002a29-0000-1000-8000-00805f9b34fb + # Manufacturer Name String + # [NEW] Characteristic + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0038/char003b + # 00002a24-0000-1000-8000-00805f9b34fb + # Model Number String + # [NEW] Characteristic + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0038/char003d + # 00002a25-0000-1000-8000-00805f9b34fb + # Serial Number String + # [NEW] Characteristic + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0038/char003f + # 00002a27-0000-1000-8000-00805f9b34fb + # Hardware Revision String + # [NEW] Characteristic + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0038/char0041 + # 00002a26-0000-1000-8000-00805f9b34fb + # Firmware Revision String + # [NEW] Characteristic + # /org/bluez/hci0/dev_EA_26_7D_4A_ED_E4/service0038/char0043 + # 00002a28-0000-1000-8000-00805f9b34fb + # Software Revision String + # [CHG] Device EA:26:7D:4A:ED:E4 UUIDs: 00001800-0000-1000-8000-00805f9b34fb + # [CHG] Device EA:26:7D:4A:ED:E4 UUIDs: 00001801-0000-1000-8000-00805f9b34fb + # [CHG] Device EA:26:7D:4A:ED:E4 UUIDs: 0000180a-0000-1000-8000-00805f9b34fb + # [CHG] Device EA:26:7D:4A:ED:E4 UUIDs: 0000181a-0000-1000-8000-00805f9b34fb + # [CHG] Device EA:26:7D:4A:ED:E4 UUIDs: e6807d20-b90a-11e5-a837-0800200c9a66 + # [CHG] Device EA:26:7D:4A:ED:E4 ServicesResolved: yes + # [CHG] Device EA:26:7D:4A:ED:E4 Appearance: 0x03c0 + # [CHG] Device EA:26:7D:4A:ED:E4 Paired: yes + # Pairing successful + # [Beddit 2564]# quit +- name: set up bluetooth + apt: + name: + - pi-bluetooth + - bluez + - libgirepository1.0-dev + # - bluez-utils + # - bluetooth + # - blueman + when: bluetooth_enabled | default(false) + tags: bluetooth + - name: install fpm with gem gem: name: fpm diff --git a/roles/sudoisbot/meta/main.yml b/roles/sudoisbot/meta/main.yml new file mode 100644 index 0000000..626a065 --- /dev/null +++ b/roles/sudoisbot/meta/main.yml @@ -0,0 +1,5 @@ +--- + +dependencies: + - mariadb + - zflux diff --git a/roles/unifi/tasks/unifi.yml b/roles/unifi/tasks/unifi.yml index 6553008..a734cc0 100644 --- a/roles/unifi/tasks/unifi.yml +++ b/roles/unifi/tasks/unifi.yml @@ -14,7 +14,7 @@ vars: prediff_cmd: echo with_items: - - "{{ unifi_url }}" + - "{{ domain }}" - name: template nginx vhost template: @@ -61,10 +61,13 @@ # v6.5.55 # v6.0.45 # v5.14.23 +# +# updated: v5.14.23 -> stable-5 -> stable-6 -> latest (v7) - name: start docker container docker_container: name: "unifi" - image: "jacobalberty/unifi:v5.14.23" + #image: "jacobalberty/unifi:v5.14.23" + image: jacobalberty/unifi:latest auto_remove: false detach: true restart_policy: "unless-stopped" @@ -86,8 +89,9 @@ - "8080:8080/tcp" # Device/ controller comm. - "127.0.0.1:8443:8443/tcp" # Controller GUI/API as seen in a web browser - "10001:10001/udp" # AP discovery - dns_servers: - - "{{ ansible_docker0.ipv4.address }}" + tags: + - unifi-container + - docker-containers - name: wait for controller to be responsive diff --git a/roles/unifi/templates/02-unifi.conf.j2 b/roles/unifi/templates/02-unifi.conf.j2 index ee4f8b6..31e9347 100644 --- a/roles/unifi/templates/02-unifi.conf.j2 +++ b/roles/unifi/templates/02-unifi.conf.j2 @@ -15,6 +15,15 @@ server { proxy_set_header Host $host; } + location /inform { + proxy_pass http://localhost:8080; + + proxy_redirect off; + proxy_set_header Host $host; + + } + + location / { proxy_pass https://localhost:8443/; @@ -32,8 +41,8 @@ server { ssl_session_timeout 5m; - ssl_certificate /usr/local/etc/certs/{{ unifi_url }}/fullchain.pem; - ssl_certificate_key /usr/local/etc/certs/{{ unifi_url }}/privkey.pem; + ssl_certificate /usr/local/etc/certs/{{ domain }}/fullchain.pem; + ssl_certificate_key /usr/local/etc/certs/{{ domain }}/privkey.pem; add_header Referrer-Policy "no-referrer" always; add_header X-Content-Type-Options "nosniff" always; diff --git a/roles/www/templates/01-sudo.is.conf.j2 b/roles/www/templates/01-sudo.is.conf.j2 index e86ea53..818d985 100644 --- a/roles/www/templates/01-sudo.is.conf.j2 +++ b/roles/www/templates/01-sudo.is.conf.j2 @@ -1,8 +1,26 @@ +server { + server_name {%- for d in server_names %} {{ d }}{% endfor %}; + {% if inventory_hostname in wg_clients -%} + listen {{ wg_clients[inventory_hostname].ip }}:443 ssl http2; + {% endif -%} + listen 443 ssl http2; + include listen-proxy-protocol.conf; + + ssl_certificate /usr/local/etc/certs/www.{{ domain }}/fullchain.pem; + ssl_certificate_key /usr/local/etc/certs/www.{{ domain }}/privkey.pem; + + location / { + return 301 https://www.$http_host$request_uri; + } + + access_log /var/log/nginx/access_{{ domain }}.log main; + error_log /var/log/nginx/error_{{ domain }}.log warn; +} server { - server_name {%- for d in server_names %} www.{{ d }} {{ d }}{% endfor %}; + server_name {%- for d in server_names %} www.{{ d }}{% endfor %}; {% if inventory_hostname in wg_clients -%} listen {{ wg_clients[inventory_hostname].ip }}:443 ssl http2;