Ter ‘lering ende vermaeck‘ hieronder een uiteenzetting van mijn Ansible-project voor de Musicplayers die ik gebruik. De MusicPlayers zijn gebaseerd op Raspberry PI’s en werken onder Debian Linux. De software die ik gebruik is in eerste instantie een kant-en-klare image van Volumio, een distri speciaal voor muziek-doeleinden gemaakt. Standaard ziet dat er zo uit (m.b.v. een web-browser):
Nu wil ik dat wijzigen in andere kleuren en mijn eigen logo. Ook een andere motd en de mogelijkheid om berichten via de mail te kunnen versturen. Het lijstje ziet er alsvolgt uit:
common:
- NTP service installeren
- Juiste tijdzone instellen voor NL
- SSH login beperken tot authenticated key-pair
- Crontab jobs kunnen maken
- ssmtp/mailutils installeren
- ssmtp configureren voor mijn mail-provider
- IP adres laten mail na opstarten
- player instellen als Roon-device
- player instellen als SqueezeBox (LMS) device
volumio:
- kleuren van de stylesheet aanpassen
- logo wijzigen in Digital Hifi logo
- NL webradio stations toevoegen
- wachtwoord instellen op AirPlay
Stap 1: Inventory
De eerste stap is het aanmaken van de inventory. In een directory genaamd /apps/ansible/musicplayers/ ziet het bestand ‘inventory‘ er zo uit:
[musicplayers] huiskamer ansible_host=192.168.1.31 ansible_user=volumio demoruimte ansible_host=192.168.1.21 ansible_user=volumio badkamer ansible_host=192.168.1.41 ansible_user=volumio tuin ansible_host=192.168.1.51 ansible_user=volumio
Stap 2: ansible.cfg
Dit bestand komt in dezelfde directory en bevat verwijzingen naar o.a. de inventory file. Ook wil ik voorkomen dat er retry-bestanden aangemaakt worden als een play op een bepaalde host niet lukt en het resultaat hoeft niet in saycows balloons te worden weergegeven. Het bestand ansible.cfg ziet er zo uit:
[defaults] nocows = 1 inventory = ./inventory retry_files_enabled = false
Stap 3: aanmaken van de variabelen
De Ansible-scripts gebruiken variabelen en die plaatsen we in een directory ‘defaults‘. Hierin komt dan een bestand genaamd ‘main.yml‘ met daarin de variabelen. Aangezien we twee roles gebruiken, maak ik voor elke role een aparte default-vars file.
roles/common/defaults/main.yml:
--- # Variables listed here are applicable to common ntpserver0: 0.nl.pool.ntp.org ntpserver1: 1.nl.pool.ntp.org ntpserver2: 2.nl.pool.ntp.org ntpserver3: 3.nl.pool.ntp.org ssmtp_mail_to: "wiedanook@hotmail.com" ssmtp_mail_from: "iemand.bij@ziggo.nl" squeezelite_server_address: 192.168.1.35 airplay_password: "wijzig_dit_wachtwoord"
roles/volumio/defaults/main.yml:
--- # Variables listed here are applicable to volumio stylesheet_digitalhifi_color: "#136F9A" stylesheet_volumio_color1: "#54c698" stylesheet_volumio_color2: "#3c763d" stylesheet_volumio_color3: "#54c688" JazzFM_uri: "http://tx.sharp-stream.com/icecast.php?i=jazzfmmobile.mp3" RadioM_uri: "http://icecast.omroep.nl/rtvutrecht-radio-m-bb-mp3" Radio10_uri: "http://playerservices.streamtheworld.com/api/livestream-redirect/RADIO10.mp3" Radio3_uri: "http://icecast.omroep.nl/3fm-sb-mp3.m3u" Radio2_uri: "http://icecast.omroep.nl/radio2-bb-mp3.m3u" Radio4_uri: "http://icecast.omroep.nl/radio4-sb-mp3.m3u" Radio6_uri: "http://icecast.omroep.nl/radio6-sb-mp3.m3u" Arrow_uri: "http://www.arrow.nl/streams/Rock128kmp3.pls" BNR_uri: "http://icecast-bnr.cdp.triple-it.nl/bnr_mp3_96_06.m3u" Classic_uri: "http://provisioning.streamtheworld.com/pls/classicfm.pls" Concert_uri: "http://streams.greenhost.nl:8080/live.m3u" QMusic_uri: "http://icecast-qmusic.cdp.triple-it.nl/Qmusic_nl_live_96.mp3.m3u" Radio10GP_uri: "http://playerservices.streamtheworld.com/api/livestream-redirect/TLPSTR12.mp3" Radio1080_uri: "http://playerservices.streamtheworld.com/api/livestream-redirect/TLPSTR20.mp3" Radio1060_uri: "http://playerservices.streamtheworld.com/api/livestream-redirect/TLPSTR18.mp3" Radio10DC_uri: "http://playerservices.streamtheworld.com/api/livestream-redirect/TLPSTR23.mp3" Radio10LS_uri: "http://playerservices.streamtheworld.com/api/livestream-redirect/TLPSTR04.mp3" Radio538_uri: "http://vip-icecast.538.lw.triple-it.nl/RADIO538_MP3.m3u" Veronica_uri: "http://provisioning.streamtheworld.com/pls/VERONICA.pls" VeronicaT1000_uri: "http://provisioning.streamtheworld.com/pls/TOP1000.pls" SkyRadio_uri: "http://provisioning.streamtheworld.com/pls/skyradio.pls" SlamFM_uri: "http://vip-icecast.538.lw.triple-it.nl/SLAMFM_MP3.m3u" Sublime_uri: "http://stream.sublimefm.nl/SublimeFM_mp3.m3u" Bingo_uri: "http://icecast.omroep.nl/rtvutrecht-bingo-fm-bb-mp3" Radio2T2000_uri: "http://icecast.omroep.nl/radio2-top2000-mp3.m3u"
Verderop in dit artikel, bij de templates, zal de aanroep van de variabelen duidelijk worden. In ieder geval is het handig om één plek te hebben waar je dit bij kunt houden voor alle tasks in de role! In feite is ‘defaults’ de laagste rangorde en de variabelen kunnen later nog overschreven worden in bv. de playbooks zelf of op de CLI.
Stap 4: musicplayers.yml
Nog steeds in de directory /apps/ansible/musicplayers wordt een yaml-bestand gemaakt dat het algemene script is. Dit zal, gezien de takenlijst, twee ‘roles‘ aanroepen, common en volumio.
--- - hosts: musicplayers roles: - { role: common, tags: 'common' } - { role: volumio, tags: 'volumio' }
De hosts-group ‘musicplayers’ (uit de inventory) wordt gebruikt om te bepalen welke nodes er ingericht gaan worden.
Stap 5: aanmaken roles
De twee roles maken we aan door de volgende directory structuur aan te maken in /apps/ansible/musicplayers/:
. └── roles ├── common │ ├── defaults │ ├── files │ ├── handlers │ ├── tasks │ └── templates └── volumio ├── defaults ├── files ├── tasks └── templates
common
Stap 6: common templates
In de common-role worden templates gebruikt voor o.a. motd en mail-berichten. Templates maken gebruik van variabelen uit de ansible-facts of uit de door ons gedefinieerde variabelen (in defaults). Ansible gebruikt hiervoor Jinja2 en de extensie van templates is daarom .j2. Bijvoorbeeld de motd-template die na inloggen met ssh laat zien wat de FQDN is van de node, motd.j2:
############## {{ ansible_fqdn }} ############### __ ___ _ ____ __ / |/ /_ _______(_)____/ __ \/ /___ ___ _____ _____ / /|_/ / / / / ___/ / ___/ /_/ / / __ `/ / / / _ \/ ___/ / / / / /_/ (__ ) / /__/ ____/ / /_/ / /_/ / __/ / /_/ /_/\__,_/____/_/\___/_/ /_/\__,_/\__, /\___/_/ /____/
Voor de ntp-service plaatsen we de NTP-servers in de config-file aan de hand van de variabelen uit defaults/main.yml. Deze template noemen we ntp.conf.j2:
driftfile /var/lib/ntp/drift restrict 127.0.0.1 restrict -6 ::1 server {{ ntpserver0 }} server {{ ntpserver1 }} server {{ ntpserver2 }} server {{ ntpserver3 }} includefile /etc/ntp/crypto/pw keys /etc/ntp/keys
Zodra er een interactieve login via ssh plaats vindt op één van de Musicplayers, wordt een E-mail bericht verstuurd. De template voor dit bericht is ialogin.txt.j2:
To:{{ ssmtp_mail_to }} From:{{ ssmtp_mail_from }} Subject: Interactive Login detected! ********************************************* * Interactive login detected at MusicPlayer * ********************************************* Logged on hostname: {{ ansible_fqdn }}
Stap 7: aanmaken van de handlers
Handlers worden gebruikt om bv. services te herstarten nadat een conf-file is aangepast. Zo ook voor de cron- en ntp-services. In de ‘handlers‘ directory komt een bestand genaamd main.yml:
--- # Handler to handle common notifications. Handlers are called by other plays. # See http://docs.ansible.com/playbooks_intro.html for more information about handlers. - name: restart ntp service: name=ntp state=restarted - name: restart cron service: name=cron state=restarted
De handlers kunnen in playbooks aangeroepen worden via de (exacte) name van de handler.
Stap 8: common files
Voor het installeren van SqueezeLite is een bestand nodig en dat zetten we klaar in de files directory:
- squeezelite-armv6hf
Stap 9: common playbooks
Als eerste wordt een bestand genaamd main.yml aangemaakt waarin de basis wordt gedefinieerd. De playbooks staan in de directory tasks. Het eerste gedeelte van main.yml zorgt voor de aanroep van andere playbooks:
--- - name: Install and start the NTP services include_tasks: ntp.yml tags: ntp - name: Prevent ssh login without key-pair include_tasks: nopwlogon.yml tags: ssh - name: Install and start the cron service include_tasks: cron.yml tags: cron - name: Add additional services include_tasks: additional_services.yml
In dezelfde directory (tasks) staan deze playbooks, bijvoorbeeld ntp.yml:
--- - name: Install ntp apt: name=ntp state=present tags: ntp - name: set timezone to Europe/Amsterdam command: timedatectl set-timezone Europe/Amsterdam tags: ntp - name: Configure ntp file template: src=ntp.conf.j2 dest=/etc/ntp.conf tags: ntp notify: restart ntp - name: Start the ntp service service: name=ntp state=started enabled=yes tags: ntp
Dit zorgt ervoor dat de ntp-service aanwezig is en gestart, ook na een eventuele reboot van het systeem. Nadat de template is aangepast wordt de handler ge-notified en zal de ntp-service herstarten. We geven deze taken de tags: ntp
Een ander playbook zorgt ervoor dat inloggen via ssh beperkt is tot gebruik van een key-pair. Deze playbook heb ik nopwlogon.yml genoemd:
--- - name: Restrict ssh logon to authorized keys lineinfile: path='/etc/ssh/sshd_config' insertafter='^UsePAM yes' line='PasswordAuthentication no' state=present
Het bestand /etc/ssh/ssh_config wordt aangepast zodat plain-tekst-wachtwoorden niet gebruikt kunnen worden om in te loggen via ssh.
De overige playbooks installeren dan nog cron, vim, mailutils, ssmtp en bzip2. Als laatste stappen in de common-role installeren we de Roon en SqueezeLite clients:
- name: Install musicplayer as Roon device include_tasks: roon.yml tags: roon - name: Install musicplayer as Squeezebox device include_tasks: squeezelite.yml tags: squeezelite
Hiervoor dient dus het playbook roon.yml:
--- - name: Check if Roon is already installed stat: path: /opt/RoonBridge/VERSION register: Roon - block: - name: get Roon software for ARMv7 get_url: url: http://download.roonlabs.com/builds/roonbridge-installer-linuxarmv7hf.sh dest: /home/volumio/ - name: set execution flag file: path: /home/volumio/roonbridge-installer-linuxarmv7hf.sh mode: 0755 - name: install roon software on ARMv7 become: yes shell: 'echo Y | /home/volumio/roonbridge-installer-linuxarmv7hf.sh' tags: roon when: ansible_architecture == "armv7l" and Roon.stat.exists == False - block: - name: get Roon software for ARMv8 get_url: url: http://download.roonlabs.com/builds/roonbridge-installer-linuxarmv8hf.sh dest: /home/volumio/ - name: set execution flag file: path: /home/volumio/roonbridge-installer-linuxarmv8hf.sh mode: 0755 - name: install roon software on ARMv8 become: yes shell: 'echo Y | /home/volumio/roonbridge-installer-linuxarmv8hf.sh' tags: roon when: ansible_architecture == "armv8l" and Roon.stat.exists == False
De te installeren package is alleen nodig als die er nog niet is, daarvoor registreren we de variabele ‘Roon’. De package is afhankelijk van de processor van de Raspberry PI en aangezien de RPi 2 een ARM7 en de RPi 3 een ARM8 heeft, gaan we die opvragen via de ansible fact ‘ansible_architecture‘. Zodoende wordt de juiste package opgehaald en geïnstalleerd. De tags: en de when: clausule hoeven we slechts éénmaal te plaatsen omdat de verschillende tasks in een block: gezet zijn.
Tenslotte installeren we de squeezelite service als deze nog niet aanwezig is. Hiervoor hadden we dus squeezelite.yml ge-include in main.yml:
--- - name: Check if Squeezelite is already installed stat: path: /usr/bin/squeezelite-armv6hf register: squeeze - block: - name: Get the squeezelite file from template become: yes template: src: squeezelite.j2 dest: /etc/init.d/squeezelite owner: volumio group: volumio mode: 0755 - name: Get the squeezelite-armv6hf command file become: yes file: src: squeezelite-armv6hf dest: /usr/bin/squeezelite-armv6hf owner: volumio group: volumio mode: 0755 - name: Start and Enable the squeezelite service systemd: name: squeezelite enabled: True state: started tags: squeezelite when: squeeze.stat.exists == False
Eerst wordt gecontroleerd of de service al aanwezig is. Mocht dat een ‘False‘ opleveren dan gebruiken we een block: om deze te installeren. Hierbij wordt de executable gekopieerd vanuit de ‘files‘ directory.
Volumio
Stap 10: volumio templates
Voor web-radiostations wordt een bestand met hierin de Uri’s van de stations aangegeven. Die Uri’s halen we uit de default-variabelen en daarom is er een template voor roles/volumio/templates/my-web-radio,js2. Deze ziet er dan zo uit:
[ { "service": "webradio", "name": "JazzFM UK", "uri": "{{ JazzFM_uri }}" }, { "service": "webradio", "name": "Radio M", "uri": "{{ RadioM_uri }}" }, { "service": "webradio", "name": "Radio 10", "uri": "{{ Radio10_uri }}" } ]
Je ziet bij de Uri’s dat er variabelen gebruikt worden.
Voor de squeezelite client is er een configuratie bestand waarin de Logitech Media Server wordt aangegeven. Deze staat ook een een default variabele en we gebruiken een template squeezelite.j2 waarin o.a. dit staat:
SB_SERVER_IP="{{ squeezelite_server_address }}"
Stap 11: volumio files
In de ‘files‘ directory worden bestanden geplaatst die één-op-één gekopieerd kunnen worden naar de nodes. In dit geval is dat:
- volumio-logo.png
Beide bestanden worden met playbooks gekopieerd naar de nodes.
Stap 12: volumio tasks
De playbooks staan in tasks en dit is het eerste gedeelte van de main.yml playbook:
--- - name: Replace Volumio logo in assets/variants/volumio/ stat: path=/volumio/http/www/app/themes/volumio/assets/variants/volumio/graphics/volumio-logo.png register: variants_path tags: logo - file: src: volumio-logo.png dest: /volumio/http/www/app/themes/volumio/assets/variants/volumio/graphics/volumio-logo.png owner: volumio group: volumio mode: 0644 when: variants_path.stat.exists == True tags: logo - name: Replace volumio logo in assets stat: path=/volumio/http/www/app/themes/volumio/assets/graphics/volumio-logo.png register: assets_path tags: logo - file: src: volumio-logo.png dest: /volumio/http/www/app/themes/volumio/assets/graphics/volumio-logo.png owner: volumio group: volumio mode: 0644 when: assets_path.stat.exists == True tags: logo
Nadat gecontroleerd wordt (m.b.v. stat:) of een bepaalde directory bestaat (variants) wordt een nieuw logo op de node geplaatst in dit geval dan in twee verschillende directories. De beide taken krijgen de tags: logo. Hierna zal main.yml de overige playbooks includen, elk met hun eigen tags:
- name: Changing the values in the stylesheet(s) include_tasks: stylesheet.yml tags: css - name: Add web-radio station to favourites include_tasks: webradio.yml tags: radio - name: Put a password on Airplay include_tasks: airplayww.yml tags: airplay
Bijvoorbeeld om bepaalde waaarde uit een stylesheet-bestand (.css) te vervangen door een andere waarde. Deze waardes komen uit default-variabelen. stylesheet.yml:
--- - name: define the stylesheet find: paths: "/volumio/http/www/styles/" patterns: "*.css" register: files #debug: var=files tags: css - name: replace color1 in stylesheet replace: dest={{item.path}} replace="{{stylesheet_digitalhifi_color}}" regexp="{{stylesheet_volumio_color1}}" with_items: "{{ files.files }}" tags: css - name: replace color2 in stylesheets replace: dest={{item.path}} replace="{{stylesheet_digitalhifi_color}}" regexp="{{stylesheet_volumio_color2}}" with_items: "{{ files.files }}" tags: css
Hiermee wordt de standaard groene kleur van Volumio vervangen door de blauwe tint van Digital Hifi. Omdat de naam van het css-bestand wijzigt per versie van Volumio, zoeken we eerst deze naam op en registreren die in de var: files waarna we deze als item kunnen gebruiken in de replace: module. We geven de tags: css mee.
Om te voorkomen dat iedereen met een i-device via Airplay zijn of haar muziek naar de Musicplayer stuurt, zetten we een wachtwoord op de Airplay-service met airplayww.yml:
--- - name: Put a password on AirPlay lineinfile: path='/volumio/app/plugins/music_service/airplay_emulation/shairport-sync.conf.tmpl' regexp='password' insertafter='log_verbosity = 0;' line=' password = "{{ airplay_password }}"' state=present tags: airplay
Het wachtwoord komt uit de defaults-variabelen maar kan uiteraard overschreven worden, bv. op de CLI met de ‘-e’ optie.
Hiermee zijn de scripts gereed en kunnen deze getest worden. In de hoofd-directory /apps/ansible/musicplayers/ gebruiken we het volgende commando’s:
ansible-playbook musicplayers.yml --syntax-check
Als de script in orde zijn kunnen we ze echt uitvoeren. Voor een aantal taken is het noodzakelijk om deze met ‘sudo’ uit te voeren en daarvoor is het commando ‘become: yes’ opgenomen. Hiermee wordt wel een sudo-wachtwoord gewenst en die geven we op met de optie ‘-K’. We kunnen tevens het aantal nodes beperken tot bv. één test-node om zodoende eerst alles te testen op juiste werking. Hiervoor is de ‘-l’ optie (limit)
ansible-playbook musicplayers.yml -l huiskamer -K
Het gebruik van de tags: stelt ons in staat om de playbooks te runnen voor specifieke taken, bijvoorbeeld om het logo te kopieren op de node demoruimte:
ansible-playbook musicplayers.yml -l demoruimte -K --tags=logo
Als alle taken succesvol uitgevoerd zijn zal de MusicPlayer GUI er anders uit zien, namelijk: