Inhaltsverzeichnis

1 Installing and configuring yaVDR with Ansible

This is an experimental feature which allows to set up a yaVDR installation based on a normal Ubuntu Server 16.04.x installation using Ansible.

This Manual is written in org-mode for Emacs and can rewrite the complete ansible configuration if you call org-babel-tangle from within emacs.

To use this playbook on a Ubuntu Server Installation you need to run the following commands:

sudo apt-get install git
git clone https://github.com/yavdr/yavdr-ansible.git
cd yavdr-ansible
sudo ./install-yavdr.sh

1.1 Install script for local usage

if (( $EUID != 0 )); then
    echo "This script must be run using sudo or as root"
    exit
fi

apt-get -y install software-properties-common
# Add repository for ansible
add-apt-repository -y ppa:ansible/ansible
# update packages
apt-get update
# install required packages
apt-get -y install ansible

# TODO: run ansible on local host
ansible-playbook yavdr07.yml -b -i 'localhost_inventory' --connection=local

2 Playbooks

2.1 yavdr07.yml

The yavdr07.yml playbook sets up a fully-featured yaVDR installation:

---
# file: yavdr07.yml
# this playbook sets up a complete yaVDR 0.7 installation

- name: set up yaVDR
  hosts: all
  become: true
  roles:
     - yavdr-common             # install and configure the basic system
     - vdr                      # install vdr and related packages
     - yavdr-network            # enable network client capabilities
     - samba-install            # install samba server
     - samba-config             # configure samba server
     - nfs-server               # install nfs server
     - yavdr-xorg               # graphical session
     - yavdr-remote             # remote configuration files, services and scripts
     - autoinstall-satip        # install vdr-plugin-satip if a Sat>IP server has been found
     - autoinstall-targavfd     # install vdr-plugin-targavfd if display is connected
     - autoinstall-imonlcd      # install vdr-plugin-imonlcd if a matchind display is connected
     #- autoinstall-pv350        # install vdr-plugin-pvr350 if a matching card is detected
     #- autoinstall-dvbsddevice  # install vdr-plugin-dvbsddevice if a matching card is detected
     - grub-config          # configure grub

  handlers:
    - include: handlers/main.yml

2.2 yavdr07-headless.yml

For a headless server installation yavdr07-headless.yml is a good choice

---
# file: yavdr07-headless.yml
# this playbook set up a headless yaVDR 0.7 installation

- name: set up a headless yaVDR server
  hosts: all
  become: true
  roles:
    - yavdr-common
    - vdr
    - yavdr-network
    - samba-server
    - samba-config
    - nfs-server
    - grub-config
    - autoinstall-satip
  handlers:
    - include: handlers/main.yml

3 Hosts

This playbook can either be used to run the installation on the localhost or any other PC in the network that can be accessed via ssh. Simply add the host names or IP addresses to the hosts file in the respective section:

[localhost]
localhost connection=local

4 Group Variables

4.1 default text for templates

# file: group_vars/all

# this is the standard text to put in templates
ansible_managed_file: "*** YAVDR: ANSIBLE MANAGED FILE ***"

4.2 PPAs

branch: unstable
ppa_owner: 'ppa:yavdr'
# a list of all package repositories to be added to the installation
repositories:
  - '{{ ppa_owner }}/main'
  - '{{ ppa_owner }}/unstable-main'
  - '{{ ppa_owner }}/{{branch}}-vdr'
  - '{{ ppa_owner }}/{{branch}}-yavdr'
  - '{{ ppa_owner }}/{{branch}}-kodi'

4.3 VDR user, directories, special configuration and plugins

# properties of the user vdr and vdr-related options
vdr:
  user: vdr
  group: vdr
  uid: 666
  gid: 666
  home: /var/lib/vdr
  recdir: /srv/vdr/video
  hide_first_recording_level: false
  safe_dirnames: true  # escape characters (useful for windows clients and FAT/NTFS file systems)
  override_vdr_charset: false

# add the vdr plugins you want to install
vdr_plugins:
  - vdr-plugin-devstatus
  - vdr-plugin-markad
  - vdr-plugin-restfulapi
  - vdr-plugin-softhddevice

4.4 Media directories

# dictionary of directories for (shared) files. Automatically exported via NFS and Samba if those roles are enabled
media_dirs:
  audio: /srv/audio
  video: /srv/audio
  pictures: /srv/picture
  files: /srv/files
  backups: /srv/backups
  recordings: '{{ vdr.recdir }}'

4.5 NFS

nfs:
  insecure: false  # set to true for OS X clients or if you plan to use libnfs as unprivileged user (e.g. KODI)

4.6 Samba

samba:
  workgroup: YAVDR
  windows_compatible: '{{ vdr.safe_dirnames }}'  # set to true to disable unix extensions, enable follow symlinks and wide links

4.7 Additional packages

# additional packages you want to install
extra_packages:
  - vim
  - tree
  - w-scan
  - bpython3

4.8 System pre-configuration

#system:
#  shutdown: poweroff
grub:
  timeout: 0
  boot_options: quiet nosplash

5 Roles

5.1 yavdr-common

This role is used to set up a basic yaVDR installation. It creates the directories, installs the vdr and other useful packages.

5.1.1 default variables

This section is for reference only, please use the files in global_vars for customizations.

---
# file: roles/yavdr-common/defaults/main.yml
  1. Repositories

    You can set a list of package repositories which provide the necessary packages. Feel free to use own PPAs if you need special customization to the VDR and it’s plugins.

    branch: unstable
    repositories:
      - 'ppa:yavdr/main'
      - 'ppa:yavdr/unstable-main'
      - 'ppa:yavdr/{{branch}}-vdr'
      - 'ppa:yavdr/{{branch}}-kodi'
      - 'ppa:yavdr/{{branch}}-yavdr'
    
  2. Drivers

    Automatically installed drivers can be very useful, but if you know you need a certain driver, you can simply set it’s value to true. If you don’t want a driver to be installed, set it’s value to false.

    drivers:
      sundtek: auto
      ddvb-dkms: auto
    
  3. Additional Packages

    Add additional packages you would like to have on your installation to this list

      extra_packages:
          - vim
          - tree
          - w-scan
    
  4. VDR

    This section allows you to set the recording directory, the user and group that runs the vdr and it’s home directory.

    user
    the vdr user name
    group
    the main group for the user vdr
    uid
    the user id for the user vdr
    gid
    the group id for the group vdr
    home
    the home directory for the user vdr
    recdir
    the recording directory used by VDR
    hide_first_recording_level
    let vdr hide the first directory level of it’s recording directory so the content of multiple directories is shown merged together
    safe_dirnames
    replace special characters which are not compatible with Windows file systems and Samba shares
    override_vdr_charset
    workaround for channels with weird EPG encodings, e.g. Sky
    vdr:
        user: vdr
        group: vdr
        uid: 666
        gid: 666
        home: /var/lib/vdr
        recdir: /srv/vdr/video
        hide_first_recording_level: false
        safe_dirnames: true
        override_vdr_charset: false
    

5.1.2 tasks

yavdr-common executes the following tasks:

  1. main.yml
    1. Disable default installation of recommended packages

      This task prevents apt to automatically install all recommended dependencies for packages:

      - name: apt | prevent automatic installation of recommended packages
        template:
          src: templates/90-norecommends.j2
          dest: /etc/apt/apt.conf.d/90norecommends
      
    2. Use bash instead of dash
      - name: use bash instead of dash
        shell: |
          echo "set dash/sh false" | debconf-communicate
          dpkg-reconfigure -f noninteractive dash
      
    3. create user vdr
    4. Disable release-upgrade notifications
      - name: disable release-upgrade notifications
        lineinfile:
          dest: /etc/update-manager/release-upgrades
          backrefs: yes
          state: present
          regexp: '^(Prompt=).*$'
          line: '\1never'
      
    5. Set up package repositories
      - name: add yaVDR PPAs
        apt_repository:
            repo: '{{ item }}'
            state: present
            update_cache: yes
        with_items: '{{ repositories }}'
      
      - name: upgrade existing packages
        apt:
            upgrade: dist
            update_cache: yes
      
    6. Install essential packages
      - name: apt | install basic packages
        apt:
          name: '{{ item }}'
          state: present
          install_recommends: no
        with_items:
          - anacron
          - at
          - bash-completion
          - biosdevname
          - linux-firmware
          - psmisc
          - python-kmodpy
          - python-usb
          - python3-usb
          - software-properties-common
          - ssh
          - ubuntu-drivers-common
          - wget
          - wpasupplicant
          - usbutils
          - xfsprogs
      
    7. Install additional packages (user defined)
      - name: apt | install extra packages
        apt:
          name: '{{ item }}'
          state: present
          install_recommends: no
        with_items:
            '{{ extra_packages }}'
      
    8. Gather facts with custom modules
      - name: get information about usb and pci hardware and loaded kernel modules
        hardware_facts:
          usb: True
          pci: True
          modules: True
          gpus: True
      - debug:
          var: usb
          verbosity: 1
      - debug:
          var: pci
          verbosity: 1
      - debug:
          var: modules
          verbosity: 1
      - debug:
          var: gpus
          verbosity: 1
      
    9. create media directories

5.1.3 templates

{{ ansible_managed_file | comment('c') }}
// Recommends are as of now still abused in many packages
APT::Install-Recommends "0";
APT::Install-Suggests "0";

5.2 vdr

5.2.1 tasks

  1. install the basic vdr packages
    ---
    # file: roles/vdr/tasks/main.yml
    
    - name: apt | install basic vdr packages
      apt:
        name: '{{ item }}'
        state: present
        install_recommends: no
      with_items:
        - vdr
        - vdrctl
        - vdr-plugin-dbus2vdr
    
  2. Add svdrp/svdrp-disc to /etc/services
    - name: add svdrp to /etc/services
      lineinfile:
        dest: /etc/services
        state: present
        line: "svdrp        6419/tcp"
    
    - name: add svdrp-disc to /etc/services
      lineinfile:
        dest: /etc/services
        state: present
        line: "svdrp-disc       6419/udp"
    
  3. Set up the recording directory for the vdr user
    - name: create vdr recdir
      file:
        state: directory
        owner: '{{ vdr.user }}'
        group: '{{ vdr.group }}'
        mode: 0775
        dest: '{{ vdr.recdir }}'
    
    - name: set option to use hide-first-recording-level patch
      blockinfile:
        dest: /etc/vdr/conf.d/04-vdr-hide-first-recordinglevel.conf
        create: true
        block: |
          [vdr]
          --hide-first-recording-level
      when:
        vdr.hide_first_recording_level
    
    - name: create local dir in recdir
      file:
        state: directory
        owner: '{{ vdr.user }}'
        group: '{{ vdr.group }}'
        mode: '0775'
        dest: '{{ vdr.recdir }}/local'
      when:
        vdr.hide_first_recording_level
    
    # TODO: set recdir, user etc. in /etc/vdr/conf.d/
    
  4. Install additional vdr plugins

    The additional plugins to install can be set in the variable {{vdr_plugins}} in the group variables

    - name: apt | install additional vdr plugins
      apt:
        name: '{{ item }}'
        state: present
        install_recommends: no
      with_items:
        '{{ vdr_plugins | default({}) }}'
      notify: [ 'Restart VDR' ]
    

5.3 STARTED yavdr-network

5.3.1 default variables

install_avahi: true

5.3.2 tasks

---
# this playbook sets up network services for a yaVDR installation
#
- name: install network packages
  apt:
      name: '{{ item }}'
      state: present
      install_recommends: no
  with_items:
      - avahi-daemon
      - avahi-utils
      - biosdevname
      - ethtool
      - nfs-common
      - vdr-addon-avahi-linker
      - wakeonlan

# Does this really work? We need a way to check if an interface supports WOL - Python Skript?
# - name: check WOL capabilities of network interfaces
#   shell: 'ethtool {{ item }} | grep -Po "(?<=Supports\sWake-on:\s).*$"'
#   register: wol
#   with_items: '{% for interface in ansible_interfaces if interface != 'lo' and interface != 'bond0' %}'

5.4 STARTED nfs-server

5.4.1 tasks

- name: install and configure nfs-kernel-server
  apt:
      name: "{{ item }}"
      state: present
      install_recommends: no
  with_items:
      - nfs-kernel-server
  when:
      - '{{ install_nfs_server }}'

5.5 TODO yavdr-remote

5.5.1 default variables

5.5.2 tasks

5.5.3 templates

5.5.4 files

5.6 TODO automatic X-server configuration

  • [ ] detect connected display
  • [ ] read EDID from displays
  • [ ] create a xorg.conf for nvidia/intel/amd gpus

5.6.1 templates

# file: roles/yavdr-xorg/templates/vdr-xorg.conf
# {{ ansible_managed_file }}

[Unit]
After=x@vt7.service
Wants=x@vt7.service
BindsTo=x@vt7.service
#!/bin/bash
# {{ ansible_managed_file }}
exec openbox-session
env | grep "DISPLAY\|DBUS_SESSION_BUS_ADDRESS\|XDG_RUNTIME_DIR" > ~/.session-env
systemctl --user import-environment

5.6.2 files

5.7 yavdr-xorg

5.7.1 default variables

5.7.2 tasks

---
# file: roles/yavdr-xorg/tasks/main.yml

- name: install packages for xorg
  apt:
    name: '{{ item }}'
    state: present
  with_items:
    - xorg
    - xserver-xorg-video-all
    - xserver-xorg-input-all
    - xlogin
    - xterm
    #- yavdr-xorg
    - openbox

- name: create folders for user session
  file:
    state: directory
    dest: '{{ item }}'
    mode: '0775'
    owner: '{{ vdr.user }}'
    group: '{{ vdr.group }}'
  with_items:
    - '{{ vdr.home }}/.config/systemd/user'
    - '{{ vdr.home }}/.config/openbox/autostart'

### TODO: move to yavdr-xorg package? ###
- name: create folder for customizations of vdr.service
  file:
    state: directory
    dest: /etc/systemd/system/vdr.service.d
    mode: '0775'

- name: add dependency to X-server for vdr.service using a drop-in
  template:
    src: templates/vdr-xorg.conf
    dest: /etc/systemd/system/vdr.service.d/
### END TODO ###

- name: create .xinitrc for vdr user
  template:
      src: 'templates/.xinitrc.j2'
      dest: '/var/lib/vdr/.xinitrc'
      mode: 0755
      owner: '{{ vdr.user }}'
      group: '{{ vdr.group }}'

- name: populate autostart for openbox
  template:
      src: 'templates/autostart.j2'
      dest: '/var/lib/vdr/.config/openbox/autostart'
      mode: 0755
      owner: '{{ vdr.user }}'
      group: '{{ vdr.group }}'

- name: set a login shell for the user vdr
  user:
      name: '{{ vdr.user }}'
      shell: '/bin/bash'
      state: present
      uid: '{{ vdr.uid }}'
      groups: '{{ vdr.group }}'
      append: yes

- name: enable and start xlogin for the user vdr
  systemd:
    daemon_reload: yes
    name: 'xlogin@{{ vdr.user }}'
    enabled: yes
    state: started

5.7.3 templates

[Unit]
Description=X with verbose logging on %I
Wants=graphical.target
Before=graphical.target

[Service]
Type=forking
ExecStart=/usr/bin/x-daemon -logverbose 6 -noreset %I

5.8 display-data

5.8.1 tasks

---
# file: roles/display-data/tasks/main.yml

- name: "detect xorg configuration"
  action: xorg_facts

- debug:
    var: xorg
    verbosity: 1

5.9 nfs-server

5.9.1 tasks

- name: install nfs server packages
  apt:
    name: '{{ item }}'
    state: present
    install_recommends: no
  with_items:
    - nfs-kernel-server
    - nfs-common

- name: create /etc/exports
  template:
    src: templates/nfs-exports.j2
    dest: /etc/exports
  notify: [ 'Restart NFS Kernel Server' ]

5.9.2 templates

/srv *(rw,fsid=0,sync,no_subtree_check,all_squash,anongid={{ vdr.gid }},anonuid={{ vdr.uid }})
{% for name, path in media_dirs.iteritems() %}
{{ path }} *(rw,fsid={{ loop.index }},sync,no_subtree_check,all_squash,anongid={{ vdr.gid }},anonuid={{ vdr.uid }}{{ ',insecure' if nfs.insecure else '' }})
{% endfor %}

5.10 nfs-config

5.11 samba-install

5.11.1 tasks

# file: roles/samba-install/tasks/main.yml

- name: install samba server
  apt:
    name: '{{ item }}'
    state: present
    install_recommends: no
  with_items:
    - samba
    - samba-common
    - samba-common-bin
    - tdb-tools

5.12 samba-config

5.12.1 tasks

# file: roles/samba-config/tasks/main.yml

# TODO:
#- name: divert original smbd.conf

- name: touch smb.conf.custom
  file:
    state: touch
    dest: '/etc/samba/smb.conf.custom'
  notify: [ 'Restart Samba' ]
    
- name: expand template for smb.conf
  template:
    src: 'templates/smb.conf.j2'
    dest: '/etc/samba/smb.conf'
    #validate: 'testparm -s %s'
  notify: [ 'Restart Samba' ]

5.12.2 templates

  1. smb.conf
    1. global settings
      {{ ansible_managed_file | comment }}
      
      #======================= Global Settings =======================
      
      [global]
      
      ## Browsing/Identification ###
      
      # Change this to the workgroup/NT-domain name your Samba server will part of
         workgroup = {{ samba.workgroup }}
      
      # server string is the equivalent of the NT Description field
         server string = %h server (Samba, Ubuntu)
      
      # This will prevent nmbd to search for NetBIOS names through DNS.
         dns proxy = no
      
      #### Debugging/Accounting ####
      
      # This tells Samba to use a separate log file for each machine
      # that connects
         log file = /var/log/samba/log.%m
      
      # Cap the size of the individual log files (in KiB).
         max log size = 1000
      
      # We want Samba to log a minimum amount of information to syslog. Everything
      # should go to /var/log/samba/log.{smbd,nmbd} instead. If you want to log
      # through syslog you should set the following parameter to something higher.
         syslog = 0
      
      # Do something sensible when Samba crashes: mail the admin a backtrace
         panic action = /usr/share/samba/panic-action %d
      
      
      ####### Authentication #######
      
      # "security = user" is always a good idea. This will require a Unix account
      # in this server for every user accessing the server. See
      # /usr/share/doc/samba-doc/htmldocs/Samba3-HOWTO/ServerType.html
      # in the samba-doc package for details.
      #   security = user
      
      # You may wish to use password encryption.  See the section on
      # 'encrypt passwords' in the smb.conf(5) manpage before enabling.
         encrypt passwords = true
      
      # If you are using encrypted passwords, Samba will need to know what
      # password database type you are using.  
         passdb backend = tdbsam
      
         obey pam restrictions = yes
      
      # This boolean parameter controls whether Samba attempts to sync the Unix
      # password with the SMB password when the encrypted SMB password in the
      # passdb is changed.
         unix password sync = yes
      
      # For Unix password sync to work on a Debian GNU/Linux system, the following
      # parameters must be set (thanks to Ian Kahan <<kahan@informatik.tu-muenchen.de> for
      # sending the correct chat script for the passwd program in Debian Sarge).
         passwd program = /usr/bin/passwd %u
         passwd chat = *Enter\snew\s*\spassword:* %n\n *Retype\snew\s*\spassword:* %n\n *password\supdated\ssuccessfully* .
      
      # This boolean controls whether PAM will be used for password changes
      # when requested by an SMB client instead of the program listed in
      # 'passwd program'. The default is 'no'.
         pam password change = yes
      
      # This option controls how unsuccessful authentication attempts are mapped 
      # to anonymous connections
         map to guest = bad user
      
      {% if samba.windows_compatible %}
      # disable unix extensions and enable following symlinks
         unix extensions = no
         follow symlinks= yes
         wide links= yes
      {% endif %}
      
    2. media directories
      {% for name, path in media_dirs.iteritems() %}
      [{{ name }}]
         path = {{ path }}
         comment = {{ name }} on %h
         guest ok = yes
         writeable = yes
         browseable = yes
         create mode = 0664
         directory mode = 0775
         force user = {{ vdr.user }}
         force group = {{ vdr.group }}
         follow symlinks = yes
         wide links = yes
      
      {% endfor %}
      
    3. include custom samba exports
      include = /etc/samba/smb.conf.custom
      

5.13 TODO autoinstall-drivers

It would be nice to be able to detect if it is suitable to install those drivers:

5.13.1 sundtek for Sundtek devices (local or network connection)

Vendor-IDs:

  • eb1a:5[1b2] (alte Generation)
  • 2659:* (neuere Sticks)

5.13.2 dddvb-dkms if only newer DD cards are detected

5.13.3 media-build-experimental (up to kernel 4.8) for „old“ cards like TT S2-6400 FF

5.14 autoinstall-satip

5.14.1 tasks

---
# file roles/autoinstall-satip/tasks/main.yml

- name: "detect SAT>IP Server(s) on the network"
  action: satip_facts

- debug:
    var: satip_detected
    verbosity: 1

- name: apt | install vdr-plugin-satip if a Sat>IP server has been detected
  apt:
    name: vdr-plugin-satip
  when: satip_detected
  notify: [ 'Restart VDR' ]

5.15 autoinstall-targavfd

5.15.1 tasks

---
# file roles/autoinstall-targavfd/tasks/main.yml

- name: apt | install vdr-plugin-targavfd if connected
  apt:
    name: vdr-plugin-targavfd
  when: 
    - '"19c2:6a11" in usb'
  notify: [ 'Restart VDR' ]

5.16 autoinstall-imonlcd

5.16.1 tasks

---
# file roles/autoinstall-imonlcd/tasks/main.yml

- name: apt | install vdr-plugin-imonlcd if connected
  apt:
    name: vdr-plugin-imonlcd
  when: 
    - '"15c2:0038" in usb'
    - '"15c2:ffdc" in usb'
  notify: [ 'Restart VDR' ]

5.17 autoinstall-libcecdaemon

5.17.1 tasks

---
# file roles/autoinstall-libcec-daemon/tasks/main.yml

- name: apt | install libcec-daemon if connected
  apt:
    name: libcec-daemon
  when: 
    - '"2548:1002" in usb'

5.18 autoinstall-pvr350

5.18.1 tasks

---
# file roles/autoinstall-pvr350/tasks/main.yml

- name: apt | install vdr-plugin-pvr350 if connected
  apt:
    name: vdr-plugin-pvr350
  when: 
    - '"0070:4000" in pci'
  notify: [ 'Restart VDR' ]

5.19 TODO autoinstall-dvbhddevice

Problem: woher kommt der Treiber (AFAIK noch nicht im Kernel)? Die Firmware sollte in yavdr-firmware stecken

5.19.1 tasks

---
# file roles/autoinstall-dvbhddevice/tasks/main.yml

- name: apt | install vdr-plugin-dvbhddevice if connected
  apt:
    name: vdr-plugin-dvbhddevice
  when: 
    - '"13c2:300a" in pci'
    - '"13c2:300b" in pci'
  notify: [ 'Restart VDR' ]

5.20 autoinstall-dvbsddevice

5.20.1 tasks

---
# file roles/autoinstall-dvbsddevice/tasks/main.yml

- name: apt | install vdr-plugin-dvbsddevice if module is loaded
  apt:
    name: vdr-plugin-dvbsddevice
  when: 
    - '"dvb_ttpci" in modules'
  notify: [ 'Restart VDR' ]

5.21 template-test

---
- name: show vars
  debug: 
    var: '{{ system }}'

- name: test templates
  template:
    src: templates/test.j2
    dest: /tmp/test.txt
{{ ansible_managed_file | comment }}

Section "ServerLayout"
    Identifier     "Layout0"
    Screen      0  "Screen0"
    {% if system.x11.dualhead.enabled %}
    Screen      1  "Screen1" RightOf "Screen0"
    {% endif %}
    InputDevice    "Keyboard0" "CoreKeyboard"
    InputDevice    "Mouse0" "CorePointer"
EndSection

Section "InputDevice"
    # generated from default
    Identifier     "Mouse0"
    Driver         "mouse"
    Option         "Protocol" "auto"
    Option         "Device" "/dev/psaux"
    Option         "Emulate3Buttons" "no"
    Option         "ZAxisMapping" "4 5"
EndSection


Section "InputDevice"
    # generated from default
    Identifier     "Keyboard0"
    Driver         "kbd"
EndSection


Section "Monitor"
    Identifier     "Monitor0"
    VendorName     "Unknown"
    ModelName      "Unknown"
{% if system.x11.display.0.default == "VGA2Scart_4_3" or system.x11.display.0.default == "VGA2Scart_16_9" %}
    HorizSync       14-17
    VertRefresh     49-61
    {% if system.x11.display.0.default == "VGA2Scart_4_3" %}
    Modeline "VGA2Scart_4_3" 13.875 720 744 808 888 576 580 585 625 -HSync -Vsync interlace
    {% elif system.x11.display.0.default == "VGA2Scart_16_9" %}
    Modeline "VGA2Scart_16_9" 19 1024 1032 1120 1216 576 581 586 625 -Hsync -Vsync interlace
    {% endif %}
{% endif %}
    Option         "DPMS"
    Option         "ExactModeTimingsDVI" "True"
EndSection


{% if system.x11.dualhead.enabled == "1" %}
Section "Monitor"
    Identifier     "Monitor1"
    VendorName     "Unknown"
    ModelName      "Unknown"
{% if system.x11.display.1.default in ("VGA2Scart_4_3",  "VGA2Scart_16_9") %}
    HorizSync       14-17
    VertRefresh     49-61
    {% if system.x11.display.1.default == "VGA2Scart_4_3" %}
    Modeline "VGA2Scart_4_3" 13.875 720 744 808 888 576 580 585 625 -HSync -Vsync interlace
    {% elif system.x11.display.1.default == "VGA2Scart_16_9" %}
    Modeline "VGA2Scart_16_9" 19 1024 1032 1120 1216 576 581 586 625 -Hsync -Vsync interlace
    {% endif %}
    Option         "DPMS"
    Option         "ExactModeTimingsDVI" "True"
{% endif %}
EndSection
{% endif %}

Section "Device"
    Identifier     "Device0"
{% if system.hardware.nvidia.detected %}
    Driver         "nvidia"
    VendorName     "NVIDIA Corporation"
{% endif %}
    Screen          0
    Option         "DPI" "100x100"
{% if system.hardware.nvidia.busid %}
    BusID          "PCI: {{ system.hardware.nvidia.busid }}"
{% endif %}
    Option         "NoLogo" "True"
    Option         "UseEvents" "True"
    Option         "TripleBuffer" "False"
    Option         "AddARGBGLXVisuals" "True"
    Option         "TwinView" "0"
    Option         "DynamicTwinView" "0"
    Option         "OnDemandVBlankinterrupts" "on"
    Option         "FlatPanelProperties" "Scaling = Native"
EndSection

{% if system.x11.dualhead.enabled == "1" %}
Section "Device"
    Identifier     "Device1"
    {% if system.hardware.nvidia.detected %}
    Driver         "nvidia"
    VendorName     "NVIDIA Corporation"
    {% endif %}
    Screen          1
    {% if system.hardware.nvidia.busid %}
    BusID          "PCI: {{ system.hardware.nvidia.busid }}"
    {% endif %}
    Option         "NoLogo" "True"
    Option         "UseEvents" "True"
    Option         "TripleBuffer" "False"
    Option         "AddARGBGLXVisuals" "True"
    Option         "TwinView" "0"
    Option         "DynamicTwinView" "0"
EndSection
{% endif %}


Section "Screen"
    Identifier     "Screen0"
    Device         "Device0"
    Monitor        "Monitor0"
    DefaultDepth    24
    SubSection     "Display"
        Depth       24
{% if system.x11.display.0.default is defined and system.x11.display.0.default %}
        Modes      "{{ system.x11.display.0.default }}"{% for mode in system.x11.display.0.mode %}{% if mode != system.x11.display.0.default %} "{{ mode }}"{% endif %}{% endfor %}

{% elif system.hardware.nvidia.detected == 1 %}
        Modes      "nvidia-auto-select"
{% endif %}
    EndSubSection
{% if system.x11.display.0.default or system.x11.default %}
    {% if system.x11.display.0.device is definded and system.x11.display.0.device %}
    Option         "ConnectedMonitor" {{ system.x11.display.0.device }}
    {% else %}
    Option         "ConnectedMonitor" {{ system.x11.default }}
    {% endif %}
    #     Option         "ConnectedMonitor" "<?cs if:(?system.x11.display.0.device) ?><?cs call:fix_display_name(system.x11.display.0.device) ?><?cs else ?><?cs var:system.x11.default ?><?cs /if ?><?cs if:(?system.x11.dualhead.enabled && system.x11.dualhead.enabled == 1) ?>, <?cs call:fix_display_name(system.x11.display.1.device) ?><?cs /if ?>"
    #Option         "ConnectedMonitor"
    "<?cs if:(?system.x11.display.0.device) ?>
          <?cs call:fix_display_name(system.x11.display.0.device) ?>
     <?cs else ?>
          <?cs var:system.x11.default ?>
     <?cs /if ?>
     <?cs if:(?system.x11.dualhead.enabled && system.x11.dualhead.enabled == 1) ?>, <?cs call:fix_display_name(system.x11.display.1.device) ?><?cs /if ?>"
#     Option         "UseDisplayDevice" "<?cs if:(?system.x11.display.0.device) ?><?cs call:fix_display_name(system.x11.display.0.device) ?><?cs else ?><?cs var:system.x11.default ?><?cs /if ?>"
# <?cs /if ?>
# <?cs if:(?system.hardware.nvidia.0.edid && system.hardware.nvidia.0.edid == "1") ?>
#     Option         "CustomEDID" "<?cs call:fix_display_name(system.x11.display.0.device) ?>:/etc/X11/edid.0.yavdr"
# <?cs /if ?>
# <?cs if:(system.hardware.nvidia.detected == 1 && ?system.x11.display.0.device) ?>
#     Option          "MetaModes" "<?cs call:fix_display_name(system.x11.display.0.device) ?>: <?cs var:system.x11.display.0.default ?> { ViewPortIn=<?cs var:system.x11.display.0.viewport.in.x ?>x<?cs var:system.x11.display.0.viewport.in.y ?>, ViewPortOut=<?cs var:system.x11.display.0.viewport.out.x ?>x<?cs var:system.x11.display.0.viewport.out.y ?>+<?cs var:system.x11.display.0.viewport.out.plusx ?>+<?cs var:system.x11.display.0.viewport.out.plusy ?> }"
# <?cs each:mode = system.x11.display.0.mode ?><?cs if:(mode != system.x11.display.0.default) ?>
#     Option          "MetaModes" "<?cs call:fix_display_name(system.x11.display.0.device) ?>: <?cs var:mode ?> { ViewPortIn=<?cs var:system.x11.display.0.viewport.in.x ?>x<?cs var:system.x11.display.0.viewport.in.y ?>, ViewPortOut=<?cs var:system.x11.display.0.viewport.out.x ?>x<?cs var:system.x11.display.0.viewport.out.y ?>+<?cs var:system.x11.display.0.viewport.out.plusx ?>+<?cs var:system.x11.display.0.viewport.out.plusy ?> }"<?cs /if ?><?cs /each ?> 
{% endif %}
EndSection

{% if system.x11.dualhead.enabled == "1" %}
Section "Screen"

     Identifier     "Screen1"
     Device         "Device1"
     Monitor        "Monitor1"
     DefaultDepth    24
     SubSection     "Display"
         Depth       24
{% if system.x11.display.0.default is defined and system.x11.display.0.default %}
         Modes      "{{ system.x11.display.1.default }}"{% for mode in system.x11.display.1.mode %}{% if mode != system.x11.display.1.default %} "{{ mode }}"{% endif %}{% endfor %}

{% elif system.hardware.nvidia.detected == "1" %}
         Modes      "nvidia-auto-select"
{% endif %}
      EndSubSection

# <?cs if:(?system.x11.display.1.default && system.x11.display.1.default != "" && system.x11.display.1.default != "disabled") ?>
#     Option         "UseDisplayDevice" "<?cs call:fix_display_name(system.x11.display.1.device) ?>"
# <?cs /if ?>
# <?cs if:(?system.hardware.nvidia.1.edid && system.hardware.nvidia.1.edid == "1") ?>
#     Option         "CustomEDID" "<?cs call:fix_display_name(system.x11.display.1.device) ?>:/etc/X11/edid.1.yavdr"
# <?cs /if ?>
# <?cs if:(system.hardware.nvidia.detected == 1 && ?system.x11.display.1.device) ?>
#     Option          "MetaModes" "<?cs call:fix_display_name(system.x11.display.1.device) ?>: <?cs var:system.x11.display.1.default ?> { ViewPortIn=<?cs var:system.x11.display.1.viewport.in.x ?>x<?cs var:system.x11.display.1.viewport.in.y ?>, ViewPortOut=<?cs var:system.x11.display.1.viewport.out.x ?>x<?cs var:system.x11.display.1.viewport.out.y ?>+<?cs var:system.x11.display.1.viewport.out.plusx ?>+<?cs var:system.x11.display.1.viewport.out.plusy ?> }"
# <?cs each:mode = system.x11.display.1.mode ?><?cs if:(mode != system.x11.display.1.default) ?>
#     Option          "MetaModes" "<?cs call:fix_display_name(system.x11.display.1.device) ?>: <?cs var:mode ?> { ViewPortIn=<?cs var:system.x11.display.1.viewport.in.x ?>x<?cs var:system.x11.display.1.viewport.in.y ?>, ViewPortOut=<?cs var:system.x11.display.1.viewport.out.x ?>x<?cs var:system.x11.display.1.viewport.out.y ?>+<?cs var:system.x11.display.1.viewport.out.plusx ?>+<?cs var:system.x11.display.1.viewport.out.plusy ?> }"<?cs /if ?><?cs /each ?>
# <?cs /if ?>
EndSection
{% endif %}

Section "Extensions"
# if no open-gl OSD is needed (e.g. for vdr-sxfe):
    Option         "Composite" "Disable"
EndSection
foo:
  - bar
  - baz
  - spam

system:
  hardware:
    nvidia:
      detected: "1"
      busid: "000:2304:234"
  x11:
    dualhead:
      enabled: "0"
    display:
      0:
        mode:
          - "1920x1080_50"
        default: "nvidia-auto"

      1:
        mode:
          - "1280x720_60"

5.22 grub-config

5.22.1 default variables

system:
  shutdown: poweroff
grub:
  timeout: 0
  boot_options: quiet nosplash

5.22.2 tasks

- name: custom grub configuration for timeout and reboot halt
  template:
    src: templates/50_custom.j2
    dest: /etc/grub.d/50_custom
    mode: '0775'
  notify: [ 'Update GRUB' ]

# TODO: add special case if plymouth is used
- name: let the system boot quietly
  lineinfile:
      dest: /etc/default/grub
      state: present
      regexp: '^(GRUB_CMDLINE_LINUX_DEFAULT=")'
      line: '\1{{ grub.boot_options}}"'
      backrefs: yes
  notify: [ 'Update GRUB' ]

5.22.3 templates

#!/bin/sh
exec tail -n +3 $0

# This file is configured by the ansible configuration for yaVDR

{% if system.shutdown is defined and system.shutdown == 'reboot' %}
menuentry "PowerOff" {
    halt
}
{% endif %}

if [ "${recordfail}" = 1 ]; then
    set timeout={{ 3 if grub.timeout < 3 else grub.timeout }}
else
    set timeout={{ grub.timeout if grub.timeout is defined else 0 }}
fi

5.22.4 handlers

- name: Update GRUB
  command: update-grub
  failed_when: ('error' in grub_register_update.stderr)
  register: grub_register_update

  # TODO: Do we need to use grub-set-default?
  # https://github.com/yavdr/yavdr-utils/blob/master/events/actions/update-grub

6 Modules

This section contains custom modules for the yaVDR Playbooks. They are used to collect facts about the system and configure applications and daemons.

6.1 hardware_facts.py

#!/usr/bin/env python

# This Module collects the vendor- and device ids for USB- and PCI(e)-devices and currently loaded kernel modules.
DOCUMENTATION = '''
---
module: hardware_facts
short_description: collects facts for kernel modules, usb and pci devices
description:
     - This Module collects the vendor- and device ids for USB- and PCI(e)-devices and
       currently loaded kernel modules.
options:
    usb:
        required: False
        default: True
        description:
          - return a list of vendor- and device ids for usb devices in '04x:04x' notation

    pci:
        required: False
        default: True
        description:
          - return a list of vendor- and device ids for pci devices in '04x:04x' notation

    modules:
        required: False
        default: True
        description:
          - return a list of currently loaded kernel modules

    gpus:
        required: False
        default: True
        description:
          - return a list of devices of the pci gpu class (0x030000)
notes:
   - requres python-pyusb and python-kmodpy
requirements: [ ]
author: "Alexander Grothe <seahawk1986@gmx.de>"
'''

EXAMPLES = '''
- name: get information about usb and pci hardware and loaded kernel modules
  hardware_facts:
    usb: True
    pci: True
    modules: True
- debug:
    var: usb
- debug
    var: pci
- debug
    var: modules
- debug
    var: gpus
'''

import glob
import json
import os
import sys
import usb.core
from collections import namedtuple

import kmodpy
from ansible.module_utils.basic import *


PCIDevice = namedtuple("PCIDevice", ['idVendor', 'idProduct', 'idClass'])

def get_pci_devices():
    for device in glob.glob('/sys/devices/pci*/*:*:*/'):
        with open(os.path.join(device, 'device')) as d:
            product_id = int(d.read().strip(), 16)
        with open(os.path.join(device, 'vendor')) as d:
            vendor_id = int(d.read().strip(), 16)
        with open(os.path.join(device, 'class')) as d:
            class_id = int(d.read().strip(), 16)
        yield PCIDevice(idVendor=vendor_id, idProduct=product_id, idClass=class_id)

def format_device_list(iterator):
    return ["{:04x}:{:04x}".format(d.idVendor, d.idProduct) for d in iterator]

def format_gpu_device_list(iterator):
    def get_entries(iterator):
        for d in iterator:
            if d.idClass == 0x030000:
                yield ("{:04x}:{:04x}".format(d.idVendor, d.idProduct))
    return [entry for entry in get_entries(iterator)]

arg_specs = {
    'usb': dict(default=True, type='bool', required=False),
    'pci': dict(default=True, type='bool', required=False),
    'modules': dict(default=True, type='bool', required=False),
    'gpus': dict(default=True, type='bool', required=False),
    }


def main():
    module = AnsibleModule(argument_spec=arg_specs, supports_check_mode=True,)
    collect_usb = module.params['usb']
    collect_pci = module.params['pci']
    collect_modules = module.params['modules']
    collect_gpus = module.params['gpus']
    if collect_usb:
        usb_devices = format_device_list(usb.core.find(find_all=True))
    else:
        usb_device = []
    if collect_pci:
        pci_devices = format_device_list(get_pci_devices())
    else:
        pci_devices = []
    if collect_modules:
        k = kmodpy.Kmod()
        modules = [m[0] for m in k.loaded()]
    else:
        modules = []
    if collect_gpus:
        gpus = format_gpu_device_list(get_pci_devices())
    else:
        gpus = []
    data = {'usb': usb_devices, 'pci': pci_devices, 'modules': modules, 'gpus': gpus}
    module.exit_json(changed=False, ansible_facts=data, msg=data)


if __name__ == '__main__':  
    main()

6.2 satip_facts.py

#!/usr/bin/env python2

DOCUMENTATION = '''
---
module: hardware_facts
short_description: "check if at least one SAT>IP server responds on the network"
description:
     - This script sends a multicast message and awaits responses by Sat>IP servers.
       Returns the boolean variable 'satip_detected'
'''
EXAMPLES = '''
- name: "detect SAT>IP Server on the network"
  action: satip_facts

- debug:
    var: satip_detected
'''

import json
import socket
import sys
import time

from ansible.module_utils.basic import *

SSDP_ADDR = "239.255.255.250"
SSDP_PORT = 1900
# SSDP_MX = max delay for server response
# a value of 2s is recommended by the SAT>IP specification 1.2.2
SSDP_MX = 2
SSDP_ST = "urn:ses-com:device:SatIPServer:1"

ssdpRequest = "\r\n".join((
    "M-SEARCH * HTTP/1.1",
    "HOST: %s:%d" % (SSDP_ADDR, SSDP_PORT),
    "MAN: \"ssdp:discover\"",
    "MX: %d" % (SSDP_MX),
    "ST: %s" % (SSDP_ST),
    "\r\n"))

def main():
    module = AnsibleModule(argument_spec={}, supports_check_mode=True,)
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # according to Sat>IP Specification 1.2.2, p. 20
    # a client should send three requests within 100 ms with a ttl of 2
    sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
    sock.settimeout(SSDP_MX + 0.5)
    for _ in range(3):
        sock.sendto(ssdpRequest, (SSDP_ADDR, SSDP_PORT))
        time.sleep(0.03)
    try:
        response = sock.recv(1000)
        if response and "SERVER:" in response:
            got_response = True
        else:
            raise ValueError('No satip server detected')
    except (socket.timeout, ValueError):
        got_response = False

    module.exit_json(changed=False, ansible_facts={'satip_detected': got_response})

if __name__ == '__main__':  
    main()

6.3 xorg_facts.py

#!/usr/bin/env python2

DOCUMENTATION = '''
---
module: xorg_facts
short_description: "gather facts about connected monitors and available modelines"
description:
     - This script needs a running x-server on a given display in order to successfully call xrandr.
       The ranking uses the following factors:
       1. preferred_refreshrate
       2. preferred_resolution
       3. preferred_output
       For each element a dictionary of values (up to 4 bit [0 .. 256]) may be passed to the module.
       The rank is represented by this order of 4-Bit values:
       | rrate | resolution | output | internal score
       |    50 |  1920x1080 |   HDMI | 0b_0100_0100_0100 = 1092
       |    60 |   1280x720 |     DP | 0b_0011_0011_0011 =  819
       Returns the connected output, monitors and modelines and a suggestion for the most fitting mode in a dictionary 'xorg'
options:
    display:
        required: False
        default: ":0"
        description:
          - the DISPLAY variable to use when calling xrandr
    preferred_outpus:
        required: False
        default: {"HDMI": 4, "DP": 3, "DVI": 2, "VGA": 1, "TV": 0}
        description:
          - ranking of the preferred display connectors
    preferred_refreshrates:
        required: False
        default: {"50": 4, "60": 3, "75": 2, "30": 1, "25": 0}
        description:
          - ranking of the preferred display refreshrate
    preferred_resolutions:
        required: False
        default: {"7680x4320": 8, "3840x2160": 4, "1920x1080": 2, "1280x720": 1, "720x576": 0}
        description:
           - ranking of the preferred display resolutions
'''
EXAMPLES = '''
- name: "collect facts for connected displays"
  action: xserver_facts
    display: ":0"

- debug:
    var: xorg
'''

import ast
import json
import re
import subprocess
import sys
import time
from collections import OrderedDict, namedtuple

from ansible.module_utils.basic import *

arg_specs = {
    'display': dict(default=[":0", ":0.1"], type='list', required=False),
    'multi_display': dict(default=True, type='bool', required=False),
    'preferred_outputs': dict(default={"HDMI": 8, "DP": 4, "DVI": 2, "VGA": 1, "TV": 0}, type='dict', required=False),
    'preferred_refreshrates': dict(default={50: 8, 60: 4, 75: 3, 30: 2, 25: 1}, type='dict', required=False),
    'preferred_resolutions': dict(default={"7680x4320": 8, "3840x2160": 4, "1920x1080": 2, "1280x720": 1, "720x576": 0},
                                  type='dict', required=False),
    }

Mode = namedtuple('Mode', ['score', 'connection', 'resolution', 'refreshrate'])


class ModelineTools(object):
    def __init__(self, preferred_outputs, preferred_resolutions, preferred_refreshrates):
        self.preferred_outputs = preferred_outputs
        self.preferred_resolutions = preferred_resolutions
        self.preferred_refreshrates = preferred_refreshrates
        
    def get_score(self, connection, resolution, refreshrate):
        connection = connection.split('-')[0]
        score = self.preferred_refreshrates.get(int(refreshrate), 0)
        score = score << 4
        score += self.preferred_resolutions.get(resolution, 0)
        #score = score << 4
        #score += self.preferred_outputs.get(connection, 0)
        return score

    @staticmethod
    def cleanup_refreshrate(refreshrate):
        rrate = refreshrate.replace('+', '').replace('*', '').replace(' ', '').strip()
        return int(round(ast.literal_eval(rrate)))
    
    def sort_mode(self, mode):
        x, y = mode.resolution.split('x')
        connection = mode.connection.split('-')[0]
        return (mode.score, int(x), int(y), self.preferred_outputs.get(connection, 0))
    

def main():
    module = AnsibleModule(argument_spec=arg_specs, supports_check_mode=False,)
    display_list = module.params['display']
    preferred_outputs = module.params['preferred_outputs']
    preferred_resolutions = module.params['preferred_resolutions']
    preferred_refreshrates = module.params['preferred_refreshrates']
    mtools = ModelineTools(preferred_outputs, preferred_resolutions, preferred_refreshrates)
    modes = []
    displays = {}
    data = {}

    for display in display_list:
        # call xrandr
        try:
            xrandr_data = subprocess.check_output(['xrandr', '-q','-d', display],
                                                  universal_newlines=True)
        except: continue
    
        for line in xrandr_data.splitlines():
            if line.startswith('Screen'):
                screen = line.split(':')[0].split()[-1]
                screen = "Screen{}".format(screen)
                displays[screen] = {}

            elif 'connected' in line:
                connection = line.split()[0]
                displays[screen][connection] = {}
                if 'disconnected' in line:
                    displays[screen][connection]['connected'] = False
                else:
                    displays[screen][connection]['connected'] = True
                displays[screen][connection]['modes'] = OrderedDict(
                    sorted({}.items(), key=lambda t: t.split('_')[0]))

            elif line.startswith(' '):
                fields = filter(None, re.split(r'\s{2,}', line))
                resolution = fields[0]
                refreshrates = fields[1:]
                r = set()
                for refreshrate in refreshrates:
                    refreshrate = refreshrate.strip()
                    rrate = mtools.cleanup_refreshrate(refreshrate)
                    if len(refreshrate) < 2:
                        continue
                    if '*' in refreshrate:
                        current_mode = (resolution, rrate)
                        displays[screen][connection]['current_mode'] = current_mode
                    if '+' in refreshrate:
                        preferred_mode = (resolution, rrate)
                        displays[screen][connection]['preferred_mode'] = preferred_mode
                    r.add(mtools.cleanup_refreshrate(refreshrate))
                    modes.append(Mode(score=mtools.get_score(connection, resolution, rrate), connection=connection,
                                 resolution=resolution, refreshrate=rrate))
                    displays[screen][connection]['modes'][resolution] = sorted(r)


    data['displays'] = displays
    data['modes'] = modes
    best_mode = max(modes, key=mtools.sort_mode)
    data["best_mode"] = {
        'connection': best_mode.connection,
        'resolution': best_mode.resolution,
        'refreshrate': best_mode.refreshrate,
    }

    module.exit_json(changed=False, ansible_facts={'xorg': data})


if __name__ == '__main__':
    main()

7 Handlers

- name: Restart Samba
  systemd:
    name: smbd.service
    state: restarted
    enabled: yes
    #masked: no
  register: samba_reload

- name: Restart NFS Kernel Server
  systemd:
    name: nfs-server.service
    state: restarted
    enabled: yes
    #masked: no
  register: nfs_reload

- name: Restart VDR
  systemd:
    name: vdr.service
    state: restarted
    enabled: yes
  register: vdr_restart

Autor: Alexander Grothe

Created: 2017-03-26 So 13:57

Validate