139 KiB
Ansible Playbooks for yaVDR 0.7
- User Stories
- Introduction
- Installing and configuring yaVDR with Ansible
- Playbooks
- Hosts
- Group Variables
- Roles
- install-dependencies
- nvidia-387 drivers
- yavdr-common
- vdr
- STARTED yavdr-network
- nfs-server
- STARTED yavdr-remote
- pulseaudio
- yavdr-xorg
- samba-install
- samba-config
- STARTED autoinstall-drivers
- autoinstall-ubuntu-drivers
- autoinstall-satip
- autoinstall-targavfd
- autoinstall-imonlcd
- autoinstall-libcecdaemon
- autoinstall-pvr350
- autoinstall-hauppauge-pvr
- autoinstall-dvbhddevice
- autoinstall-dvbsddevice
- autoinstall-hardware-irmp
- kodi
- dvd
- template-test
- grub-config
- Modules
- Handlers
User Stories
yavdr-full
A User wants to install yaVDR without customization and relies on full automation.
After installing a supported Ubuntu Server version and (if needed) setting up the network connection, the install script is downloaded and started.
The install script adds the ansible PPA, installs ansible and other basic dependencies and runs the automated installation process.
Several roles are used to tie everything together:
- yavdr-common
- installs the basic set of packages needed and preconfigures the system (package sources, package manager settings, entires for network ports, directory structure etc.). Also collects information about the system
- autoinstall-ubuntu-drivers
- uses Ubuntu's automatic detection for additional drivers (e.g. proprietary nvidia driver, microcode updates, VirtualBox guest additions)
- vdr
- install the vdr package and required plugins
- pulseaudio
- install and preconfigure pulseaudio
- yavdr-network
- configure WOL, add nfs client capabilites, install avahi-linker
- samba-server
- install and preconfigure a samba server
- nfs-server
- install nfs-kernelserver and preconfigure nfs exports (and avahi-announcements)
- 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
- yavdr-xorg
- install and configure a systemd user session with a running X-server. A frontend script will manage VDR's frontend and other applications like KODI. Also choose the best possible output plugin.
- grub-config
- update grub configuration based on executed roles
Using vdr, pulseaudio and xorg together
Introduction
One of the major problems we faced with customized Ubuntu ISO files as installation media for prior yaVDR versions has been the limited hardware support and the time consuming process to create and update them. An interesting alternative to this approach is to enable the user to choose the installation medium himself so point releases, kernel versions and additional drivers can be chosen deliberately. After the basic setup is complete (and a working internet connection is available), a fully customizable install script completes the yaVDR installation.
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
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 --tags=install
Playbooks
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
- autoinstall-ubuntu-drivers # use ubuntu-drivers to install proprietary dirvers
# (e.g. nvidia, virtualbox)
# - nvidia-387 # install very recent nvidia-387 from ppa:graphics-drivers/ppa
- 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
- pulseaudio # install pulseaudio
- 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-pvr350 # install vdr-plugin-pgvr350 if a matching card is detected
- autoinstall-hauppauge-pvr # install vdr-plugin-pvrinput if a matching card is found
- autoinstall-dvbsddevice # install vdr-plugin-dvbsddevice if a matching card is detected
- kodi
- dvd # set up packages and a udev rule to allow kodi and other players
# to play and eject optical media
- grub-config # configure grub
tags:
- always
handlers:
- import_tasks: handlers/main.yml
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
tags:
- always
handlers:
- import_tasks: handlers/main.yml
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:
[local]
localhost connection=local
Group Variables
default text for templates used by ansible
---
# file: group_vars/all
# this is the standard text to put in templates
ansible_managed_file: "*** YAVDR: ANSIBLE MANAGED FILE ***"
PPAs
branch: unstable
ppa_owner: 'ppa:yavdr'
# add the following PPAs
repositories:
- '{{ ppa_owner }}/main'
- '{{ ppa_owner }}/unstable-main'
- '{{ ppa_owner }}/{{branch}}-vdr'
- '{{ ppa_owner }}/{{branch}}-yavdr'
- '{{ ppa_owner }}/{{branch}}-kodi'
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
confdir: /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-vpp
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 }}'
NFS
nfs:
insecure: false # set to true for OS X clients or if you plan to use libnfs as unprivileged user (e.g. KODI)
Samba
samba:
workgroup: YAVDR
windows_compatible: '{{ vdr.safe_dirnames }}' # set to true to disable unix extensions, enable follow symlinks and wide links
Additional packages
# additional packages you want to install
extra_packages:
- bpython
- bpython3
- htop
- tree
- vdr-addon-acpiwakeup
- vim
- w-scan
System pre-configuration
frontend: vdr
#system:
# shutdown: poweroff
grub:
timeout: 0
boot_options: quiet nosplash
Roles
install-dependencies
tasks
main.yml
---
- name: apt | install packages
apt:
name: '{{ item }}'
state: present
install_recommends: no
with_items:
'{{ packages }}'
nvidia-387 drivers
install nvidia-387 from ppa:graphics-drivers/ppa
tasks
main.yml
---
- name: add ppa:graphics-drivers/ppa
apt_repository:
repo: 'ppa:graphics-drivers/ppa'
state: present
update_cache: yes
- name: install nvidia-387 and other required packages
apt:
name: '{{ item }}'
state: present
install_recommends: no
with_items:
- nvidia-387
- nvidia-settings
yavdr-common
This role is used to set up a basic yaVDR installation. It creates the directories, installs the vdr and other useful packages.
default variables
This section is for reference only, please use the files in global_vars
for customizations.
---
# file: roles/yavdr-common/defaults/main.yml
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'
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
Additional Packages
Add additional packages you would like to have on your installation to this list
extra_packages:
- vim
- tree
- w-scan
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
tasks
yavdr-common executes the following tasks:
main.yml
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
Use bash instead of dash
- name: use bash instead of dash
shell: |
echo "set dash/sh false" | debconf-communicate
dpkg-reconfigure -f noninteractive dash
create user vdr
Disable release-upgrade notifications
- name: disable release-upgrade notifications
lineinfile:
dest: /etc/update-manager/release-upgrades
backrefs: yes
state: present
regexp: '^(Prompt=).*$'
line: '\1never'
Set up package repositories
- name: add PPAs
apt_repository:
repo: '{{ item }}'
state: present
update_cache: yes
with_items: '{{ repositories }}'
- name: run apt-get dist-upgrade
apt:
upgrade: dist
update_cache: yes
Install essential packages
- name: apt | install basic packages
apt:
name: '{{ item }}'
state: present
install_recommends: no
with_items:
- anacron
- acl
- at
- bash-completion
#- biosdevname # caution: may change device names after a minimal installation!
- debconf-utils
- linux-firmware
- psmisc
- python-kmodpy
- python-requests
- python-usb
- python3-usb
- software-properties-common
- ssh
- wget
- wpasupplicant
- usbutils
- xfsprogs
Install additional packages (user defined)
- name: apt | install extra packages
apt:
name: '{{ item }}'
state: present
install_recommends: no
with_items:
'{{ extra_packages }}'
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: '{{ item }}'
verbosity: 1
with_items:
- usb
- pci
- gpus
- modules
create media directories
templates
{{ ansible_managed_file | comment('c') }}
// Recommends are as of now still abused in many packages
APT::Install-Recommends "0";
APT::Install-Suggests "0";
vdr
tasks
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
Add svdrp/svdrp-disc to /etc/services
- name: add svdrp and svdrp-disc to /etc/services
lineinfile:
dest: /etc/services
state: present
line: "{{ item }}"
with_items:
- "svdrp 6419/tcp"
- "svdrp-disc 6419/udp"
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/
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' ]
copy vdr configuration files (if they don't exist yet)
- name: ensure vdr is stopped
systemd:
name: vdr.service
state: stopped
notify: [ 'Start VDR' ]
- name: "vdr configuration | copy remote.conf if it doesn't exist yet"
copy:
src: files/remote.conf
dest: '{{ vdr.confdir }}/remote.conf'
owner: '{{ vdr.user }}'
group: '{{ vdr.group }}'
mode: 0644
force: no
- name: "vdr configuration | copy keymacros.conf if it doesn't exist yet"
copy:
src: files/keymacros.conf
dest: '{{ vdr.confdir }}/keymacros.conf'
owner: '{{ vdr.user }}'
group: '{{ vdr.group }}'
mode: 0644
force: no
templates
files
remote.conf
LIRC.Up KEY_UP
LIRC.Down KEY_DOWN
LIRC.Menu KEY_MENU
LIRC.Ok KEY_OK
LIRC.Back KEY_ESC
LIRC.Left KEY_LEFT
LIRC.Right KEY_RIGHT
LIRC.Red KEY_RED
LIRC.Green KEY_GREEN
LIRC.Yellow KEY_YELLOW
LIRC.Blue KEY_BLUE
LIRC.0 KEY_0
LIRC.1 KEY_1
LIRC.2 KEY_2
LIRC.3 KEY_3
LIRC.4 KEY_4
LIRC.5 KEY_5
LIRC.6 KEY_6
LIRC.7 KEY_7
LIRC.8 KEY_8
LIRC.9 KEY_9
LIRC.Info KEY_INFO
LIRC.Play KEY_PLAY
LIRC.Pause KEY_PAUSE
LIRC.Play/Pause KEY_PLAYPAUSE
LIRC.Stop KEY_STOP
LIRC.Record KEY_RECORD
LIRC.FastFwd KEY_FASTFORWARD
LIRC.FastRew KEY_REWIND
LIRC.Next KEY_NEXT
LIRC.Prev KEY_BACK
LIRC.Power KEY_POWER2
LIRC.Channel+ KEY_CHANNELUP
LIRC.Channel- KEY_CHANNELDOWN
LIRC.PrevChannel KEY_PREVIOUS
LIRC.Volume+ KEY_VOLUMEUP
LIRC.Volume- KEY_VOLUMEDOWN
LIRC.Mute KEY_MUTE
LIRC.Subtitles KEY_SUBTITLE
LIRC.Schedule KEY_EPG
LIRC.Channels KEY_CHANNEL
LIRC.Commands KEY_FAVORITES
LIRC.Audio KEY_MODE
LIRC.Timers KEY_TIME
LIRC.Recordings KEY_PVR
LIRC.Setup KEY_SETUP
LIRC.User0 KEY_TEXT
LIRC.User1 KEY_PROG1
LIRC.User2 KEY_PROG2
LIRC.User3 KEY_PROG3
LIRC.User4 KEY_PROG4
LIRC.User5 KEY_AUDIO
LIRC.User6 KEY_VIDEO
LIRC.User7 KEY_IMAGES
LIRC.User8 KEY_FN
LIRC.User9 KEY_SCREEN
XKeySym.Up Up
XKeySym.Down Down
XKeySym.Menu Home
XKeySym.Menu Tab
XKeySym.Menu KP_Home
XKeySym.Ok Return
XKeySym.Ok KP_Enter
XKeySym.Back BackSpace
XKeySym.Back KP_Separator
XKeySym.Left Left
XKeySym.Right Right
XKeySym.Up KP_Up
XKeySym.Down KP_Down
XKeySym.Left KP_Left
XKeySym.Right KP_Right
XKeySym.Red F1
XKeySym.Green F2
XKeySym.Yellow F3
XKeySym.Blue F4
XKeySym.0 0
XKeySym.1 1
XKeySym.2 2
XKeySym.3 3
XKeySym.4 4
XKeySym.5 5
XKeySym.6 6
XKeySym.7 7
XKeySym.8 8
XKeySym.9 9
XKeySym.0 KP_0
XKeySym.1 KP_1
XKeySym.2 KP_2
XKeySym.3 KP_3
XKeySym.4 KP_4
XKeySym.5 KP_5
XKeySym.6 KP_6
XKeySym.7 KP_7
XKeySym.8 KP_8
XKeySym.9 KP_9
XKeySym.Info End
XKeySym.Info KP_End
XKeySym.Pause F9
XKeySym.FastFwd F6
XKeySym.FastRew F5
XKeySym.Power Pause
XKeySym.Volume+ F12
XKeySym.Volume- F11
XKeySym.Volume+ KP_Add
XKeySym.Volume- KP_Subtract
XKeySym.Mute F10
XKeySym.User1 F5
XKeySym.User2 F6
XKeySym.User3 F7
XKeySym.User4 F8
XKeySym.User5 Print
XKeySym.User6 Scroll_Lock
XKeySym.User7 Insert
XKeySym.User8 KP_Divide
XKeySym.User9 KP_Multiply
XKeySym.Audio Menu
XKeySym.Channel+ Prior
XKeySym.Channel- Next
XKeySym.Channel+ KP_Prior
XKeySym.Channel- KP_Next
XKeySym.Volume+ XF86AudioRaiseVolume
XKeySym.Volume- XF86AudioLowerVolume
XKeySym.Mute XF86AudioMute
XKeySym.Stop XF86AudioStop
XKeySym.Play/Pause XF86AudioPlay
XKeySym.Prev XF86AudioPrev
XKeySym.Next XF86AudioNext
keymacros.conf
# Remote control key macros for VDR
#
# Format:
#
# macrokey key1 key2 key3...
# macrokey @plugin key1 key2 key3...
#
# See man vdr(5)
Red Recordings
Green Schedule
Yellow Info
Blue Timers
User0 @osdteletext
STARTED yavdr-network
default variables
install_avahi: true
tasks
---
# this playbook sets up network services for a yaVDR installation
- name: apt |install packages for network services
apt:
name: '{{ item }}'
state: present
install_recommends: no
with_items:
- avahi-daemon
- avahi-utils
#- biosdevname # caution: this may change device names after a minimal installation!
- 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' %}'
- name: restart autofs if running
systemd:
name: autofs
state: restarted
enabled: yes
masked: no
- name: ensure autofs is running
systemd:
name: autofs
state: started
enabled: yes
masked: no
- name: restart avahi-linker if running
systemd:
name: avahi-linker
state: restarted
enabled: yes
masked: no
- name: start avahi-linker
systemd:
name: avahi-linker
state: started
enabled: yes
masked: no
- name: enable and start additional services for avahi-linker
systemd:
name: '{{ item }}'
state: started
enabled: yes
masked: no
with_items:
- vdr-net-monitor
- vdr-update-monitor
- prevent-umount-on-pause
nfs-server
TODO avahi-services für NFS beim Stoppen des nfs-kernel-servers depublizieren, beim Start wieder announcen
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' ]
- name: populate /etc/avahi/services
template:
src: templates/avahi/service.j2
dest: '/etc/avahi/services/yavdr-{{ item.key }}.service'
with_dict: '{{ media_dirs }}'
templates
/etc/exports
/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 %}
avahi services
<?xml version="1.0" standalone='no'?>
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
<service-group>
<name replace-wildcards="yes">{{ item.key|capitalize }} on %h</name> ## Name
<service>
<type>_nfs._tcp</type>
<port>2049</port>
<txt-record>path={{ item.value }}</txt-record> ## path to shared Folder
<txt-record>subtype={{ 'vdr' if item.key == 'recordings' else item.key }}</txt-record> ## subtype
</service>
</service-group>
STARTED yavdr-remote
default variables
tasks
---
# This role is used to set up the yaVDR remote control configuration.
- name: apt | install yavdr-remote
apt:
name: yavdr-remote
state: present
- name: apt | install eventlircd
apt:
name: eventlircd
state: present
- name: apt | install lirc
apt:
name: lirc
state: present
- name: stop, mask and disable lircd.socket, lircd.service and lircd-uinput.service # (the default lirc configuration conflicts with eventlircd)
systemd:
name: '{{ item }}'
enabled: no
state: stopped
masked: yes
with_items:
- lircd.service
- lircd.socket
- lircd-uinput.service
ignore_errors: yes
- name: configure vdr to read from a lircd-compatible socket
template:
src: templates/03-vdr-lirc.conf.j2
dest: /etc/vdr/conf.d/03-vdr-lirc.conf
notify: ['Restart VDR']
# TODO: upload lircd2uinput package to PPA
#- name: install lircd2uinput
# tag: install
# apt:
# name: lircd2uinput
# state: present
# when:
# install_eventlircd is defined and install_eventlircd
templates
{% if frontend != 'kodi' %}
[vdr]
--lirc=/var/run/lirc/lircd
{% endif %}
files
pulseaudio
tasks
---
- name: apt | install pulseaudio and pavucontrol
apt:
name: '{{ item }}'
state: present
install_recommends: no
with_items:
- pulseaudio
- pavucontrol
- name: create /etc/asound.conf with pulseaudio as default device
template:
src: templates/alsa/asound.conf.j2
dest: /etc/asound.conf
templates
# Use PulseAudio by default
pcm.!default {
type pulse
fallback "sysdefault"
hint {
show on
description "Default ALSA Output (currently PulseAudio Sound Server)"
}
}
ctl.!default {
type pulse
fallback "sysdefault"
}
yavdr-xorg
About the GUI session
The X-Server is started by using the two systemd units xlogin@.service
and x@.service
provided by the package xlogin. The former is enabled (and started) for the vdr user - which results (using the default settings for the user vdr with the uid 666) in the activation of xlogin@vdr.service
when reaching the graphical.target.
x@vt7.service
is started automatically as a dependency of xlogin@vdr.service
and starts the X-server. xlogin@vdr.service
also starts a systemd user session using user@666.service
.
To prevent stopping the X-server when vdr is running, additional dependencies are set up - see the drop-in rules created in /etc/systemd/system/vdr.service.d/
.
Basic Services like the session DBus, pulseaudio etc. are started for the default.target
of the session.
The openbox autostart script is used to update the systemd session with the needed session variables. It creates a file ~/.session-env
which is used as an environment file by vdr.service
(so the vdr knows the DISPLAY and can access pulseaudio).
The autostart script then enables all services to be pulled in by yavdr-desktop.target. As the last step yavdr-desktop.target
is startet, which results in staring yavdr-frontend.service
and additional Units for a second display (openbox and a browser for osd2eb, if available).
In order to achive a clean shutdown of the session, x@t7.service
is set as a dependency of the systemd unit instance user@666.service
and all processes within the session must be shutdown properly when stopping xlogin@vdr.service
. If systemd units are used within the user session, they must stop their process(es) successfully (you should set them up to accept expected exit codes appropriately). The window manager openbox is started using the ~/.xinitrc
and stopped using the systemd unit exit-wm.service
(which runs on stopping the jobs pulled in by the default.target
of the session).
default variables
tasks
---
# file: roles/yavdr-xorg/tasks/main.yml
- import_tasks: setup-xorg.yml tags=install,update
- import_tasks: detect-xorg.yml tags=xorg:detect,install
- import_tasks: desktop-session.yml tags=install,update
---
# file: roles/yavdr-xorg/tasks/setup-xorg.yml
- name: create directories for systemd drop-in files
file:
dest: '{{ item }}'
state: directory
with_items:
- /etc/systemd/system/x@.service.d/
- /etc/systemd/system/vdr.service.d/
- '/etc/systemd/system/user@{{ vdr.uid }}.service.d/'
- 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/vdr-xorg.conf
- name: start x@.service before xlogin@.service
template:
src: templates/x@service.d/xlogin.conf.j2
dest: /etc/systemd/system/x@.service.d/xlogin.conf
- name: load environment file for vdr.service
template:
src: templates/systemd/vdr-environ.j2
dest: /etc/systemd/system/vdr.service.d/load-environ.conf
- name: user@{{ vdr.uid }}.service depends on x@vt7.service
template:
src: templates/user@666.service.d/x-dependency.conf.j2
dest: '/etc/systemd/system/user@{{ vdr.uid }}.service.d/x-dependency.conf'
- name: start softhddevice detached and set audio to pulseaudio
lineinfile:
path: /etc/vdr/conf.avail/softhddevice.conf
line: '{{ item }}'
with_items:
- '-D'
# - '-a pulse' # do we need this with our existing asound.conf?
- 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: apt | install packages for xorg
apt:
name: '{{ item }}'
state: present
with_items:
- xlogin
- xorg
- xserver-xorg-input-all
- xserver-xorg-video-all
- openbox
- python3-dbus2vdr
- python3-yavdrfrontend
- read-edid
- tmux
- vdr-plugin-desktop
#- yavdr-xorg
- name: apt | install desktop programs
apt:
name: '{{ item }}'
state: present
with_items:
- xterm
- firefox
- kiosk-browser
---
# file: roles/yavdr-xorg/tasks/detect-xorg.yml
- name: "expand template for x-verbose@.service"
template:
src: "templates/systemd/system/x-verbose@.service.j2"
dest: "/etc/systemd/system/x-verbose@.service"
- name: "expand template for xorg-verbose.conf"
template:
src: "templates/xorg-verbose.conf.j2"
dest: "/etc/X11/xorg-verbose.conf"
- name: Stop VDR
systemd:
name: vdr.service
state: stopped
enabled: yes
notify: ['Start VDR']
- name: Stop xlogin
systemd:
name: 'xlogin@{{ vdr.user }}.service'
state: stopped
- name: Stop x
systemd:
name: x@vt7.service
state: stopped
- name: "start x-verbose@.service"
systemd:
name: "x-verbose@vt7.service"
state: started
enabled: false
masked: false
daemon_reload: true
- name: "wait a little bit, so X has some time to start up (needed?)"
wait_for:
timeout: 3
- name: "detect xorg configuration"
action: xrandr_facts
- debug:
var: xorg.primary
- debug:
var: xorg.secondary
when: xorg.secondary is defined
- name: "stop x-verbose@vt7.service"
systemd:
name: "x-verbose@vt7.service"
state: stopped
enabled: false
masked: true
# TODO: expand template for xorg.conf (or snippets)
# with respect for the available graphics card driver
# nvidia, noveau, intel, radeon
- name: create xorg.conf (for nvidia driver)
template:
src: templates/xorg.conf.j2
dest: /etc/X11/xorg.conf
backup: yes
---
# file: roles/yavdr-xorg/tasks/desktop-session.yml
- name: create folders for user configuration files
file:
state: directory
dest: '{{ item }}'
mode: '0775'
owner: '{{ vdr.user }}'
group: '{{ vdr.group }}'
with_items:
- '{{ vdr.home }}/.config/systemd/user'
- '{{ vdr.home }}/.config/openbox'
- '{{ vdr.home }}/.config/pulse'
- name: expand template for .xinitrc for vdr user
template:
src: 'templates/.xinitrc.j2'
dest: '{{ vdr.home }}/.xinitrc'
mode: 0755
owner: '{{ vdr.user }}'
group: '{{ vdr.group }}'
- name: expand template for openbox autostart
template:
src: 'templates/openbox/autostart.j2'
dest: '{{ vdr.home }}/.config/openbox/autostart'
mode: 0755
owner: '{{ vdr.user }}'
group: '{{ vdr.group }}'
- name: expand rc.xml for openbox
template:
src: 'templates/openbox/rc.xml.j2'
dest: '{{ vdr.home }}/.config/openbox/rc.xml'
mode: 0644
owner: '{{ vdr.user }}'
group: '{{ vdr.group }}'
- name: create yavdr-desktop.target for the user session
template:
src: 'templates/systemd/user/yavdr-desktop.target.j2'
dest: '{{ vdr.home }}/.config/systemd/user/yavdr-desktop.target'
mode: 0644
owner: '{{ vdr.user }}'
group: '{{ vdr.group }}'
- name: disable pulseaudio autospawning
lineinfile:
path: '{{ vdr.home }}/.config/pulse/client.conf'
line: 'autospawn = no'
create: yes
state: present
owner: '{{ vdr.user }}'
group: '{{ vdr.group }}'
- name: create tmux.service for the session
template:
src: roles/yavdr-xorg/templates/systemd/user/tmux.service.j2
dest: '{{ vdr.home }}/.config/systemd/user/tmux.service'
- name: create wm-exit.service for the session
template:
src: roles/yavdr-xorg/templates/systemd/user/wm-exit.service.j2
dest: '{{ vdr.home }}/.config/systemd/user/wm-exit.service'
- name: create detect-second-display.service for the session
template:
src: templates/systemd/user/detect-second-display.service.j2
dest: '{{ vdr.home }}/.config/systemd/user/detect-second-display.service'
- name: create openbox-second.service for the session
template:
src: templates/systemd/user/openbox-second.service.j2
dest: '{{ vdr.home }}/.config/systemd/user/openbox-second.service'
- name: create osd2web.service for the session
template:
src: templates/systemd/user/osd2web.service.j2
dest: '{{ vdr.home }}/.config/systemd/user/osd2web.service'
- name: link /usr/bin/start-desktop to /var/lib/vdr/plugins/desktop/starter
file:
src: /usr/bin/start-desktop
dest: /var/lib/vdr/plugins/desktop/starter
state: link
- name: enable and start xlogin for the vdr user
systemd:
daemon_reload: yes
name: 'xlogin@{{ vdr.user }}'
enabled: yes
state: started
templates
xorg
x-verbose@.service
# file: roles/yavdr-xorg/templates/systemd/system/x-verbose@.service.j2
[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 -config /etc/X11/xorg-verbose.conf
Drop-in snippet for x@.service
[Unit]
Before=xlogin@{{ vdr.user }}.service
[Unit]
Wants=x@vt7.service
After=x@vt7.service
[Service]
TimeoutStopSec=20
KillMode=mixed
Drop-in snippet for vdr.service
# file: roles/yavdr-xorg/templates/vdr-xorg.conf
# {{ ansible_managed_file }}
[Unit]
After=x@vt7.service
Wants=x@vt7.service
#BindsTo=x@vt7.service
.xinitrc
#!/bin/bash
# {{ ansible_managed_file }}
exec openbox-session
xorg.conf
Section "Device"
Identifier "nvidia"
Driver "nvidia"
Option "NoLogo" "true"
Option "DynamicTwinView" "true"
Option "NoFlip" "false"
# Option "FlatPanelProperties" "Scaling = Native"
# Option "ModeValidation" "NoVesaModes, NoXServerModes"
# Option "ModeDebug" "true"
# Option "HWCursor" "false"
EndSection
Section "Screen"
Identifier "screen"
Device "nvidia"
EndSection
Section "Extensions"
Option "Composite" "false"
EndSection
{{ ansible_managed_file | comment }}
Section "ServerLayout"
Identifier "Layout0"
Screen 0 "Screen0" 0 0
{% if xorg.secondary is defined %}
Screen 1 "Screen1" RightOf "Screen0"
{% endif %}
#InputDevice "Keyboard0" "CoreKeyboard"
#InputDevice "Mouse0" "CorePointer"
Option "Xinerama" "0"
EndSection
Section "InputClass"
Identifier "exclude eventlircd devices"
MatchTag "eventlircd"
Option "Ignore" "True"
EndSection
Section "Monitor"
Identifier "Monitor0"
VendorName "{{ xorg.primary.vendor }}"
ModelName "{{ xorg.primary.model }}"
Option "DPMS"
Option "ExactModeTimingsDVI" "True"
EndSection
Section "Device"
Identifier "Device0"
Driver "nvidia"
VendorName "NVIDIA Corporation"
Option "NoLogo" "true"
{% if xorg.primary.bus_id is defined %}
BoardName "{{ xorg.primary.gpu_name }}"
BusID "{{ xorg.primary.bus_id }}"
{% else %}
BoardName "Unknown"
{% endif %}
Screen 0
EndSection
Section "Screen"
Identifier "Screen0"
Device "Device0"
Monitor "Monitor0"
DefaultDepth 24
Option "nvidiaXineramaInfoOrder" "{{ xorg.primary.connector }}"
Option "ConnectedMonitor" "{{ xorg.primary.connector }}{% if xorg.secondary is defined %}, {{ xorg.secondary.connector }}{% endif %}"
Option "UseDisplayDevice" "{{ xorg.primary.connector }}"
Option "CustomEDID" "{{ xorg.primary.connector }}:/etc/X11/edid.{{ xorg.primary.connector }}.bin"
Option "metamodes" "{{ xorg.primary.connector }}: {{ xorg.primary.mode }} +0+0"
Option "SLI" "Off"
Option "MultiGPU" "Off"
Option "BaseMosaic" "off"
SubSection "Display"
Depth 24
EndSubSection
EndSection
{% if xorg.secondary is defined %}
Section "Device"
Identifier "Device1"
Driver "nvidia"
VendorName "NVIDIA Corporation"
Option "NoLogo" "true"
{% if xorg.secondary.bus_id is defined %}
BoardName "{{ xorg.primary.gpu_name }}"
BusID "{{ xorg.primary.bus_id }}"
{% else %}
BoardName "Unknown"
{% endif %}
Screen 1
EndSection
Section "Monitor"
Identifier "Monitor1"
VendorName "{{ xorg.secondary.vendor }}"
ModelName "{{ xorg.secondary.model }}"
EndSection
Section "Screen"
Identifier "Screen1"
Device "Device1"
Monitor "Monitor1"
DefaultDepth 24
Option "nvidiaXineramaInfoOrder" "{{ xorg.secondary.connector }}"
Option "ConnectedMonitor" "{{ xorg.secondary.connector }}"
Option "UseDisplayDevice" "{{ xorg.secondary.connector }}"
Option "CustomEDID" "{{ xorg.secondary.connector }}:/etc/X11/edid.{{ xorg.secondary.connector }}.bin"
Option "metamodes" "{{ xorg.secondary.connector }}: {{ xorg.secondary.mode }} +0+0"
Option "SLI" "Off"
Option "MultiGPU" "Off"
Option "BaseMosaic" "off"
SubSection "Display"
Depth 24
EndSubSection
EndSection
{% endif %}
{{ ansible_managed_file | comment }}
Section "ServerLayout"
Identifier "Layout0"
Screen 0 "Screen0"
{% if xorg.best_tv_mode.secondary is defined %}
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
# ignore devices with eventlircd tag
# ENV{ID_INPUT.tags}+="eventlircd"
# must be set by an udev rule
Section "InputClass"
Identifier "ignore eventlircd devices"
MatchTag "eventlircd"
Option "Ignore" "True"
EndSection
Section "InputDevice"
# generated from default
Identifier "Keyboard0"
Driver "kbd"
EndSection
Section "Monitor"
Identifier "Monitor0"
VendorName "Unknown"
ModelName "Unknown"
{# TODO: VGA2SCART support (if needed)
{% 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 xrandr.best_tv_mode.secondary is defined %}
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 is defined and system.hardware.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 xrandr.best_tv_mode.secondary is defined %}
Section "Device"
Identifier "Device1"
{% if system.hardware.nvidia.detected %}
Driver "nvidia"
VendorName "NVIDIA Corporation"
{% endif %}
Screen 1
{% if system.hardware.nvidia.busid is defined and 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 xrandr.best_tv_mode.primary is defined %}
Modes "{{ xrandr.best_tv_mode.primary.mode }}"{#{% for mode in xrandr %}{% if mode != system.x11.display.0.default %} "{{ mode }}"{% endif %}{% endfor %}#} nvidia-auto-select
{% 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 %}
vdr
[Service]
Environment=XDG_RUNTIME_DIR=/run/user/{{ vdr.uid }}/
EnvironmentFile=-/var/lib/vdr/.session-env
Systemd User Session
yavdr-desktop.target
[Unit]
Description=yaVDR Desktop
Requires=default.target
After=default.target pulseaudio.service
Wants=dbus.service pulseaudio.service
AllowIsolate=yes
detect-second-display
[Unit]
Description=Detect second DISPLAY using xrandr
[Service]
Type=oneshot
ExecStart=/bin/bash -c 'export DISPLAY=$DISPLAY.1; xrandr -q | grep "connected" && echo "DISPLAY=$DISPLAY" > ~/.second_display || rm -f ~/.second_display'
[Install]
WantedBy=yavdr-desktop.target
openbox-second.service
[Unit]
Description=Start openbox on the second DISPLAY if it exists
After=detect-second-display.service
Wants=detect-second-display.service
Before=yavdr-frontend.service
ConditionFileNotEmpty=%h/.second_display
[Service]
PassEnvironment=XDG_RUNTIME_DIR DBUS_SESSION_BUS_ADDRESS
EnvironmentFile=%h/.second_display
ExecStart=/usr/bin/openbox --config-file %h/.config/openbox/rc.xml
[Install]
WantedBy=yavdr-desktop.target
osd2web.service
[Unit]
Description=Start a kiosk browser on the second DISPLAY if it exists
After=detect-second-display.service openbox-second.service
Wants=detect-second-display.service openbox-second.service
ConditionFileNotEmpty=%h/.second_display
[Service]
EnvironmentFile=%h/.second_display
PassEnvironment=XDG_RUNTIME_DIR DBUS_SESSION_BUS_ADDRESS
ExecStart=/usr/bin/on_vdr -o -c 'kiosk-browser "http://localhost:4444/skins/horchiTft/index.html?theme=blue&onlyView=1"'
KillSignal=SIGINT
[Install]
WantedBy=yavdr-desktop.target
tmux.service
[Unit]
Description=Start tmux in detached session
[Service]
EnvironmentFile=-%h/.session-env
Type=forking
ExecStart=/usr/bin/tmux new-session -s use-session -d
ExecStop=/usr/bin/tmux kill-session -t %u
Restart=always
[Install]
WantedBy=default.target
wm-exit.service
[Unit]
Description=exit window manager gracefully
[Service]
ExecStart=/bin/true
ExecStop=/usr/bin/openbox --exit
RemainAfterExit=True
[Install]
WantedBy=default.target
openbox
autostart
# forward environment variables to an environment file and the systemd user session
env | grep "DISPLAY\|DBUS_SESSION_BUS_ADDRESS\|XDG_RUNTIME_DIR" > ~/.session-env
systemctl --user import-environment DISPLAY XAUTHORITY XDG_RUNTIME_DIR DBUS_SESSION_BUS_ADDRESS
enabled_services=(
"tmux.service" "detect-second-display.service"
"openbox-second.service" "osd2web.service"
"yavdr-frontend.service" "pulseaudio.socket"
"pulseaudio.service" "wm-exit.service")
disabled_services=()
# enable configured services for the user session
for service in "${enabled_services[@]}"; do
systemctl --user enable "$service"
done
# disable unwanted services for the user session
for service in "${disabled_services[@]}"; do
systemctl --user disable "$service"
done
if which dbus-update-activation-environment >/dev/null 2>&1; then
dbus-update-activation-environment DISPLAY XAUTHORITY XDG_RUNTIME_DIR
fi
# start systemd units for the yavdr user session
systemctl --user isolate yavdr-desktop.target
rc.xml
<?xml version="1.0" encoding="UTF-8"?>
<openbox_config xmlns="http://openbox.org/3.4/rc" xmlns:xi="http://www.w3.org/2001/XInclude">
<!-- Do not edit this file, it will be overwritten on install.
Copy the file to $HOME/.config/openbox/ instead. -->
<resistance>
<strength>10</strength>
<screen_edge_strength>20</screen_edge_strength>
</resistance>
<focus>
<focusNew>yes</focusNew>
<!-- always try to focus new windows when they appear. other rules do
apply -->
<followMouse>no</followMouse>
<!-- move focus to a window when you move the mouse into it -->
<focusLast>yes</focusLast>
<!-- focus the last used window when changing desktops, instead of the one
under the mouse pointer. when followMouse is enabled -->
<underMouse>no</underMouse>
<!-- move focus under the mouse, even when the mouse is not moving -->
<focusDelay>200</focusDelay>
<!-- when followMouse is enabled, the mouse must be inside the window for
this many milliseconds (1000 = 1 sec) before moving focus to it -->
<raiseOnFocus>no</raiseOnFocus>
<!-- when followMouse is enabled, and a window is given focus by moving the
mouse into it, also raise the window -->
</focus>
<placement>
<policy>Smart</policy>
<!-- 'Smart' or 'UnderMouse' -->
<center>yes</center>
<!-- whether to place windows in the center of the free area found or
the top left corner -->
<monitor>Active</monitor>
<!-- with Smart placement on a multi-monitor system, try to place new windows
on: 'Any' - any monitor, 'Mouse' - where the mouse is, 'Active' - where
the active window is, 'Primary' - only on the primary monitor -->
<primaryMonitor>Active</primaryMonitor>
<!-- The monitor where Openbox should place popup dialogs such as the
focus cycling popup, or the desktop switch popup. It can be an index
from 1, specifying a particular monitor. Or it can be one of the
following: 'Mouse' - where the mouse is, or
'Active' - where the active window is -->
</placement>
<theme>
<name>Onyx</name>
<titleLayout>NLIMC</titleLayout>
<!--
available characters are NDSLIMC, each can occur at most once.
N: window icon
L: window label (AKA title).
I: iconify
M: maximize
C: close
S: shade (roll up/down)
D: omnipresent (on all desktops).
-->
<keepBorder>yes</keepBorder>
<animateIconify>yes</animateIconify>
<font place="ActiveWindow">
<name>sans</name>
<size>8</size>
<!-- font size in points -->
<weight>bold</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="InactiveWindow">
<name>sans</name>
<size>8</size>
<!-- font size in points -->
<weight>bold</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="MenuHeader">
<name>sans</name>
<size>9</size>
<!-- font size in points -->
<weight>normal</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="MenuItem">
<name>sans</name>
<size>9</size>
<!-- font size in points -->
<weight>normal</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="ActiveOnScreenDisplay">
<name>sans</name>
<size>9</size>
<!-- font size in points -->
<weight>bold</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
<font place="InactiveOnScreenDisplay">
<name>sans</name>
<size>9</size>
<!-- font size in points -->
<weight>bold</weight>
<!-- 'bold' or 'normal' -->
<slant>normal</slant>
<!-- 'italic' or 'normal' -->
</font>
</theme>
<desktops>
<!-- this stuff is only used at startup, pagers allow you to change them
during a session
these are default values to use when other ones are not already set
by other applications, or saved in your session
use obconf if you want to change these without having to log out
and back in -->
<number>2</number>
<firstdesk>1</firstdesk>
<names>
<!-- set names up here if you want to, like this:
<name>desktop 1</name>
<name>desktop 2</name>
-->
</names>
<popupTime>875</popupTime>
<!-- The number of milliseconds to show the popup for when switching
desktops. Set this to 0 to disable the popup. -->
</desktops>
<resize>
<drawContents>yes</drawContents>
<popupShow>Nonpixel</popupShow>
<!-- 'Always', 'Never', or 'Nonpixel' (xterms and such) -->
<popupPosition>Center</popupPosition>
<!-- 'Center', 'Top', or 'Fixed' -->
<popupFixedPosition>
<!-- these are used if popupPosition is set to 'Fixed' -->
<x>10</x>
<!-- positive number for distance from left edge, negative number for
distance from right edge, or 'Center' -->
<y>10</y>
<!-- positive number for distance from top edge, negative number for
distance from bottom edge, or 'Center' -->
</popupFixedPosition>
</resize>
<!-- You can reserve a portion of your screen where windows will not cover when
they are maximized, or when they are initially placed.
Many programs reserve space automatically, but you can use this in other
cases. -->
<margins>
<top>0</top>
<bottom>0</bottom>
<left>0</left>
<right>0</right>
</margins>
<dock>
<position>TopLeft</position>
<!-- (Top|Bottom)(Left|Right|)|Top|Bottom|Left|Right|Floating -->
<floatingX>0</floatingX>
<floatingY>0</floatingY>
<noStrut>no</noStrut>
<stacking>Above</stacking>
<!-- 'Above', 'Normal', or 'Below' -->
<direction>Vertical</direction>
<!-- 'Vertical' or 'Horizontal' -->
<autoHide>no</autoHide>
<hideDelay>300</hideDelay>
<!-- in milliseconds (1000 = 1 second) -->
<showDelay>300</showDelay>
<!-- in milliseconds (1000 = 1 second) -->
<moveButton>Middle</moveButton>
<!-- 'Left', 'Middle', 'Right' -->
</dock>
<keyboard>
<chainQuitKey>C-g</chainQuitKey>
<!-- Keybindings for desktop switching -->
<keybind key="C-A-Left">
<action name="GoToDesktop">
<to>left</to>
<wrap>no</wrap>
</action>
</keybind>
<keybind key="C-A-Right">
<action name="GoToDesktop">
<to>right</to>
<wrap>no</wrap>
</action>
</keybind>
<keybind key="C-A-Up">
<action name="GoToDesktop">
<to>up</to>
<wrap>no</wrap>
</action>
</keybind>
<keybind key="C-A-Down">
<action name="GoToDesktop">
<to>down</to>
<wrap>no</wrap>
</action>
</keybind>
<keybind key="S-A-Left">
<action name="SendToDesktop">
<to>left</to>
<wrap>no</wrap>
</action>
</keybind>
<keybind key="S-A-Right">
<action name="SendToDesktop">
<to>right</to>
<wrap>no</wrap>
</action>
</keybind>
<keybind key="S-A-Up">
<action name="SendToDesktop">
<to>up</to>
<wrap>no</wrap>
</action>
</keybind>
<keybind key="S-A-Down">
<action name="SendToDesktop">
<to>down</to>
<wrap>no</wrap>
</action>
</keybind>
<keybind key="W-F1">
<action name="GoToDesktop">
<to>1</to>
</action>
</keybind>
<keybind key="W-F2">
<action name="GoToDesktop">
<to>2</to>
</action>
</keybind>
<keybind key="W-F3">
<action name="GoToDesktop">
<to>3</to>
</action>
</keybind>
<keybind key="W-F4">
<action name="GoToDesktop">
<to>4</to>
</action>
</keybind>
<keybind key="W-d">
<action name="ToggleShowDesktop"/>
</keybind>
<!-- Keybindings for windows -->
<keybind key="A-F4">
<action name="Close"/>
</keybind>
<keybind key="A-Escape">
<action name="Lower"/>
<action name="FocusToBottom"/>
<action name="Unfocus"/>
</keybind>
<keybind key="A-space">
<action name="ShowMenu">
<menu>client-menu</menu>
</action>
</keybind>
<!-- Take a screenshot of the current window with scrot when Alt+Print are pressed -->
<keybind key="A-Print">
<action name="Execute">
<command>scrot -s</command>
</action>
</keybind>
<!-- Keybindings for window switching -->
<keybind key="A-Tab">
<action name="NextWindow">
<finalactions>
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</finalactions>
</action>
</keybind>
<keybind key="A-S-Tab">
<action name="PreviousWindow">
<finalactions>
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</finalactions>
</action>
</keybind>
<keybind key="C-A-Tab">
<action name="NextWindow">
<panels>yes</panels>
<desktop>yes</desktop>
<finalactions>
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</finalactions>
</action>
</keybind>
<!-- Keybindings for window switching with the arrow keys -->
<keybind key="W-S-Right">
<action name="DirectionalCycleWindows">
<direction>right</direction>
</action>
</keybind>
<keybind key="W-S-Left">
<action name="DirectionalCycleWindows">
<direction>left</direction>
</action>
</keybind>
<keybind key="W-S-Up">
<action name="DirectionalCycleWindows">
<direction>up</direction>
</action>
</keybind>
<keybind key="W-S-Down">
<action name="DirectionalCycleWindows">
<direction>down</direction>
</action>
</keybind>
<!-- Keybindings for running applications -->
<keybind key="W-e">
<action name="Execute">
<startupnotify>
<enabled>true</enabled>
<name>Konqueror</name>
</startupnotify>
<command>kfmclient openProfile filemanagement</command>
</action>
</keybind>
<!-- Launch scrot when Print is pressed -->
<keybind key="Print">
<action name="Execute">
<command>scrot</command>
</action>
</keybind>
</keyboard>
<mouse>
<dragThreshold>1</dragThreshold>
<!-- number of pixels the mouse must move before a drag begins -->
<doubleClickTime>500</doubleClickTime>
<!-- in milliseconds (1000 = 1 second) -->
<screenEdgeWarpTime>400</screenEdgeWarpTime>
<!-- Time before changing desktops when the pointer touches the edge of the
screen while moving a window, in milliseconds (1000 = 1 second).
Set this to 0 to disable warping -->
<screenEdgeWarpMouse>false</screenEdgeWarpMouse>
<!-- Set this to TRUE to move the mouse pointer across the desktop when
switching due to hitting the edge of the screen -->
<context name="Frame">
<mousebind button="A-Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="A-Left" action="Click">
<action name="Unshade"/>
</mousebind>
<mousebind button="A-Left" action="Drag">
<action name="Move"/>
</mousebind>
<mousebind button="A-Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="A-Right" action="Drag">
<action name="Resize"/>
</mousebind>
<mousebind button="A-Middle" action="Press">
<action name="Lower"/>
<action name="FocusToBottom"/>
<action name="Unfocus"/>
</mousebind>
<mousebind button="A-Up" action="Click">
<action name="GoToDesktop">
<to>previous</to>
</action>
</mousebind>
<mousebind button="A-Down" action="Click">
<action name="GoToDesktop">
<to>next</to>
</action>
</mousebind>
<mousebind button="C-A-Up" action="Click">
<action name="GoToDesktop">
<to>previous</to>
</action>
</mousebind>
<mousebind button="C-A-Down" action="Click">
<action name="GoToDesktop">
<to>next</to>
</action>
</mousebind>
<mousebind button="A-S-Up" action="Click">
<action name="SendToDesktop">
<to>previous</to>
</action>
</mousebind>
<mousebind button="A-S-Down" action="Click">
<action name="SendToDesktop">
<to>next</to>
</action>
</mousebind>
</context>
<context name="Titlebar">
<mousebind button="Left" action="Drag">
<action name="Move"/>
</mousebind>
<mousebind button="Left" action="DoubleClick">
<action name="ToggleMaximize"/>
</mousebind>
<mousebind button="Up" action="Click">
<action name="if">
<shaded>no</shaded>
<then>
<action name="Shade"/>
<action name="FocusToBottom"/>
<action name="Unfocus"/>
<action name="Lower"/>
</then>
</action>
</mousebind>
<mousebind button="Down" action="Click">
<action name="if">
<shaded>yes</shaded>
<then>
<action name="Unshade"/>
<action name="Raise"/>
</then>
</action>
</mousebind>
</context>
<context name="Titlebar Top Right Bottom Left TLCorner TRCorner BRCorner BLCorner">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Middle" action="Press">
<action name="Lower"/>
<action name="FocusToBottom"/>
<action name="Unfocus"/>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="ShowMenu">
<menu>client-menu</menu>
</action>
</mousebind>
</context>
<context name="Top">
<mousebind button="Left" action="Drag">
<action name="Resize">
<edge>top</edge>
</action>
</mousebind>
</context>
<context name="Left">
<mousebind button="Left" action="Drag">
<action name="Resize">
<edge>left</edge>
</action>
</mousebind>
</context>
<context name="Right">
<mousebind button="Left" action="Drag">
<action name="Resize">
<edge>right</edge>
</action>
</mousebind>
</context>
<context name="Bottom">
<mousebind button="Left" action="Drag">
<action name="Resize">
<edge>bottom</edge>
</action>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="ShowMenu">
<menu>client-menu</menu>
</action>
</mousebind>
</context>
<context name="TRCorner BRCorner TLCorner BLCorner">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Left" action="Drag">
<action name="Resize"/>
</mousebind>
</context>
<context name="Client">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Middle" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
</context>
<context name="Icon">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
<action name="ShowMenu">
<menu>client-menu</menu>
</action>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="ShowMenu">
<menu>client-menu</menu>
</action>
</mousebind>
</context>
<context name="AllDesktops">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="ToggleOmnipresent"/>
</mousebind>
</context>
<context name="Shade">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="ToggleShade"/>
</mousebind>
</context>
<context name="Iconify">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="Iconify"/>
</mousebind>
</context>
<context name="Maximize">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Middle" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="ToggleMaximize"/>
</mousebind>
<mousebind button="Middle" action="Click">
<action name="ToggleMaximize">
<direction>vertical</direction>
</action>
</mousebind>
<mousebind button="Right" action="Click">
<action name="ToggleMaximize">
<direction>horizontal</direction>
</action>
</mousebind>
</context>
<context name="Close">
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
<action name="Unshade"/>
</mousebind>
<mousebind button="Left" action="Click">
<action name="Close"/>
</mousebind>
</context>
<context name="Desktop">
<mousebind button="Up" action="Click">
<action name="GoToDesktop">
<to>previous</to>
</action>
</mousebind>
<mousebind button="Down" action="Click">
<action name="GoToDesktop">
<to>next</to>
</action>
</mousebind>
<mousebind button="A-Up" action="Click">
<action name="GoToDesktop">
<to>previous</to>
</action>
</mousebind>
<mousebind button="A-Down" action="Click">
<action name="GoToDesktop">
<to>next</to>
</action>
</mousebind>
<mousebind button="C-A-Up" action="Click">
<action name="GoToDesktop">
<to>previous</to>
</action>
</mousebind>
<mousebind button="C-A-Down" action="Click">
<action name="GoToDesktop">
<to>next</to>
</action>
</mousebind>
<mousebind button="Left" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
<mousebind button="Right" action="Press">
<action name="Focus"/>
<action name="Raise"/>
</mousebind>
</context>
<context name="Root">
<!-- Menus -->
<mousebind button="Middle" action="Press">
<action name="ShowMenu">
<menu>client-list-combined-menu</menu>
</action>
</mousebind>
<mousebind button="Right" action="Press">
<action name="ShowMenu">
<menu>root-menu</menu>
</action>
</mousebind>
</context>
<context name="MoveResize">
<mousebind button="Up" action="Click">
<action name="GoToDesktop">
<to>previous</to>
</action>
</mousebind>
<mousebind button="Down" action="Click">
<action name="GoToDesktop">
<to>next</to>
</action>
</mousebind>
<mousebind button="A-Up" action="Click">
<action name="GoToDesktop">
<to>previous</to>
</action>
</mousebind>
<mousebind button="A-Down" action="Click">
<action name="GoToDesktop">
<to>next</to>
</action>
</mousebind>
</context>
</mouse>
<menu>
<!-- You can specify more than one menu file in here and they are all loaded,
just don't make menu ids clash or, well, it'll be kind of pointless -->
<!-- default menu file (or custom one in $HOME/.config/openbox/) -->
<!-- system menu files on Debian systems -->
<file>/var/lib/openbox/debian-menu.xml</file>
<file>menu.xml</file>
<hideDelay>200</hideDelay>
<!-- if a press-release lasts longer than this setting (in milliseconds), the
menu is hidden again -->
<middle>no</middle>
<!-- center submenus vertically about the parent entry -->
<submenuShowDelay>100</submenuShowDelay>
<!-- time to delay before showing a submenu after hovering over the parent
entry.
if this is a negative value, then the delay is infinite and the
submenu will not be shown until it is clicked on -->
<submenuHideDelay>400</submenuHideDelay>
<!-- time to delay before hiding a submenu when selecting another
entry in parent menu
if this is a negative value, then the delay is infinite and the
submenu will not be hidden until a different submenu is opened -->
<showIcons>yes</showIcons>
<!-- controls if icons appear in the client-list-(combined-)menu -->
<manageDesktops>yes</manageDesktops>
<!-- show the manage desktops section in the client-list-(combined-)menu -->
</menu>
<!--
# this is an example with comments through out. use these to make your
# own rules, but without the comments of course.
# you may use one or more of the name/class/role/title/type rules to specify
# windows to match
<application name="the window's _OB_APP_NAME property (see obxprop)"
class="the window's _OB_APP_CLASS property (see obxprop)"
groupname="the window's _OB_APP_GROUP_NAME property (see obxprop)"
groupclass="the window's _OB_APP_GROUP_CLASS property (see obxprop)"
role="the window's _OB_APP_ROLE property (see obxprop)"
title="the window's _OB_APP_TITLE property (see obxprop)"
type="the window's _OB_APP_TYPE property (see obxprob)..
(if unspecified, then it is 'dialog' for child windows)">
# you may set only one of name/class/role/title/type, or you may use more
# than one together to restrict your matches.
# the name, class, role, and title use simple wildcard matching such as those
# used by a shell. you can use * to match any characters and ? to match
# any single character.
# the type is one of: normal, dialog, splash, utility, menu, toolbar, dock,
# or desktop
# when multiple rules match a window, they will all be applied, in the
# order that they appear in this list
# each rule element can be left out or set to 'default' to specify to not
# change that attribute of the window
<decor>yes</decor>
# enable or disable window decorations
<shade>no</shade>
# make the window shaded when it appears, or not
<position force="no">
# the position is only used if both an x and y coordinate are provided
# (and not set to 'default')
# when force is "yes", then the window will be placed here even if it
# says you want it placed elsewhere. this is to override buggy
# applications who refuse to behave
<x>center</x>
# a number like 50, or 'center' to center on screen. use a negative number
# to start from the right (or bottom for <y>), ie -50 is 50 pixels from
# the right edge (or bottom). use 'default' to specify using value
# provided by the application, or chosen by openbox, instead.
<y>200</y>
<monitor>1</monitor>
# specifies the monitor in a xinerama setup.
# 1 is the first head, or 'mouse' for wherever the mouse is
</position>
<size>
# the size to make the window.
<width>20</width>
# a number like 20, or 'default' to use the size given by the application.
# you can use fractions such as 1/2 or percentages such as 75% in which
# case the value is relative to the size of the monitor that the window
# appears on.
<height>30%</height>
</size>
<focus>yes</focus>
# if the window should try be given focus when it appears. if this is set
# to yes it doesn't guarantee the window will be given focus. some
# restrictions may apply, but Openbox will try to
<desktop>1</desktop>
# 1 is the first desktop, 'all' for all desktops
<layer>normal</layer>
# 'above', 'normal', or 'below'
<iconic>no</iconic>
# make the window iconified when it appears, or not
<skip_pager>no</skip_pager>
# asks to not be shown in pagers
<skip_taskbar>no</skip_taskbar>
# asks to not be shown in taskbars. window cycling actions will also
# skip past such windows
<fullscreen>yes</fullscreen>
# make the window in fullscreen mode when it appears
<maximized>true</maximized>
# 'Horizontal', 'Vertical' or boolean (yes/no)
</application>
# end of the example
-->
<applications>
<application title="softhddevice">
<decor>no</decor>
<maximized>true</maximized>
<!--<skip_pager>yes</skip_pager>-->
<skip_taskbar>no</skip_taskbar>
</application>
<application title="browser">
<decor>no</decor>
<maximized>true</maximized>
<!--<skip_pager>yes</skip_pager>-->
<skip_taskbar>yes</skip_taskbar>
</application>
<application title="osd2Web">
<decor>no</decor>
<maximized>true</maximized>
<!--<skip_pager>yes</skip_pager>-->
<skip_taskbar>yes</skip_taskbar>
</application>
</applications>
</openbox_config>
samba-install
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
samba-config
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' ]
templates
smb.conf
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 %}
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 %}
include custom samba exports
include = /etc/samba/smb.conf.custom
STARTED autoinstall-drivers
It would be nice to be able to detect if it is suitable to install those drivers:
TODO sundtek for Sundtek devices (local or network connection)
Vendor-IDs:
- eb1a:5[1b2] (alte Generation)
- 2659:* (neuere Sticks)
TODO dddvb-dkms if only newer DD cards are detected
TODO media-build-experimental (up to kernel 4.8) for "old" cards like TT S2-6400 FF
TODO newly merged DD drivers
autoinstall-ubuntu-drivers
The tool ubuntu-drivers is used to install the matching driver version for nvidia graphics cards, virtualbox guest additions and Intel and AMD microcode updates.
tasks
---
# file roles/autoinstall-ubuntu-drivers/tasks/main.yml
- name: apt | install ubuntu-drivers-common
apt:
name: ubuntu-drivers-common
state: present
- name: ensure /etc/yavdr exists
file:
path: /etc/yavdr
state: directory
mode: 0755
- name: let ubuntu-drivers automatically install additional drivers
command: ubuntu-drivers --package-list /etc/yavdr/autoinstalled autoinstall
autoinstall-satip
If a Sat>IP Server responds to a discovery request, the package vdr-plugin-satip is installed.
tasks
---
# file roles/autoinstall-satip/tasks/main.yml
- name: "detect SAT>IP Server(s) on the network"
action: satip_facts
- debug:
var: satip_devices
verbosity: 1
- name: apt | install vdr-plugin-satip if a Sat>IP server has been detected
apt:
name: vdr-plugin-satip
when: satip_devices
notify: [ 'Restart VDR' ]
autoinstall-targavfd
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' ]
autoinstall-imonlcd
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 or "15c2:ffdc" in usb'
notify: [ 'Restart VDR' ]
autoinstall-libcecdaemon
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
autoinstall-pvr350
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' ]
autoinstall-hauppauge-pvr
---
# file roles/autoinstall-hauppauge-pvr/tasks/main.yml
- name: apt | install vdr-plugin-pvrinput if a haupauge pvr card is found
apt:
name: vdr-plugin-pvrinput
when: '"0070:4000" in pci or "4444:0016" in pci'
notify: [ 'Restart VDR' ]
TODO autoinstall-dvbhddevice
Problem: woher kommt der Treiber (AFAIK noch nicht im Kernel)? Die Firmware sollte in yavdr-firmware stecken
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 or "13c2:300b" in pci'
notify: [ 'Restart VDR' ]
autoinstall-dvbsddevice
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' ]
autoinstall-hardware-irmp
tasks
---
# file roles/autoinstall-hardware-irmp/tasks/main.yml
- name: apt | install yavdr-hardware-irmp if connected
apt:
name: yavdr-hardware-irmp
when: '"1209:4444" in usb'
TODO add other device ids if necessary
kodi
tasks
Install KODI
main.yml
install-kodi.yml
configure-kodi.yml
templates
kodi.service
This systemd unit for the user session starts (and stops) kodi.
files
Those configuration files provide a preconfiguration for kodi which overrides the system wide configuration
Lircmap.xml
This file allows to map keys sent by remotes via eventlircd (which uses the name devinput
) to kodi key names.
keymaps
This file maps the keys defined in Lircmap.xml to actions within kodi.
remote.xml
dvd
tasks
install libdvd-pkg, allow programs to eject optical media
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
# ignore devices with eventlircd tag
# ENV{ID_INPUT.tags}+="eventlircd"
# must be set by an udev rule
Section "InputClass"
Identifier "ignore eventlircd devices"
MatchTag "eventlircd"
Option "Ignore" "True"
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 %}
{% if not system.x11.use_compositing %}
Section "Extensions"
# if not open-gl OSD is needed (e.g. for vdr-sxfe):
Option "Composite" "Disable"
EndSection
{% endif %}
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"
grub-config
default variables
system:
shutdown: poweroff
grub:
timeout: 0
boot_options: quiet nosplash
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' ]
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
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
Modules
This section contains custom modules for the yaVDR Playbooks. They are used to collect facts about the system and configure applications and daemons.
hardware_facts.py
# 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 pciPath')
vendor_dict = {
0x10de: 'nvidia',
0x8086: 'intel',
0x1002: 'amd',
0x80ee: 'virtualbox',
}
def get_pci_devices():
for device in glob.glob('/sys/devices/pci*/*:*:*/*:*:*/'):
try:
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, pciPath=device)
except IOError:
pass
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 {"VendorName": vendor_dict.get(d.idVendor, "unknown"),
"VendorID": d.idVendor, "ProductID": 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()
satip_facts.py
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 a list of detected SAT>IP servers with their name and capabilites.
'''
EXAMPLES = '''
- name: "detect SAT>IP Server on the network"
action: satip_facts
- debug:
var: satip_devices
'''
import json
import socket
import sys
import time
import xml.etree.ElementTree as ET
import requests
from contextlib import contextmanager
from ansible.module_utils.basic import *
SSDP_BIND = "0.0.0.0"
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"))
@contextmanager
def socket_manager(*args, **kwargs):
"""provide a context manager for socket"""
sock = socket.socket(*args, **kwargs)
sock.setblocking(False)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
except socket.error:
pass
sock.settimeout(SSDP_MX + 0.5)
sock.bind((SSDP_BIND, SSDP_PORT))
try:
yield sock
finally:
sock.close()
def parse_satip_xml(data):
""" Parse SAT>IP XML data.
Args:
data (str): XML input data..
Returns:
dict: Parsed SAT>IP device name and frontend information.
"""
result = {'name': '', 'frontends': {}}
if data:
root = ET.fromstring(data)
name = root.find('.//*/{urn:schemas-upnp-org:device-1-0}friendlyName')
result['name'] = name.text
satipcap = root.find('.//*/{urn:ses-com:satip}X_SATIPCAP')
if satipcap is None:
raise ValueError("Invalid SAT>IP device description")
caps = {}
for system in satipcap.text.split(","):
cap = system.split("-")
if cap:
count = int(cap[1])
if cap[0] in caps:
count = count + caps[cap[0]]
caps[cap[0]] = count
result['frontends'] = caps
return result
def main():
description_urls = []
device_list = []
module = AnsibleModule(argument_spec={}, supports_check_mode=True,)
with socket_manager(socket.AF_INET, socket.SOCK_DGRAM) as sock:
# according to Sat>IP Specification 1.2.2, p. 20
# a client should send three requests within 100 ms with a ttl of 2
for _ in range(3):
sock.sendto(ssdpRequest, (SSDP_ADDR, SSDP_PORT))
time.sleep(0.03)
try:
response = sock.recv(1024)
if response and "SERVER:" in response:
for line in response.splitlines():
if "LOCATION" in line:
url = line.split()[-1].strip()
if url not in description_urls:
description_urls.append(url)
info = requests.get(url, timeout=2)
device_list.append(parse_satip_xml(info.text))
else:
raise ValueError('No satip server detected')
except (socket.timeout, ValueError, ET.ParseError):
pass
module.exit_json(changed=False, ansible_facts={'satip_devices': device_list})
if __name__ == '__main__':
main()
xrandr_facts.py
- support multiple screens (-d :0.0 .. :0.n)
from __future__ import print_function
import ast
import binascii
import csv
import re
import subprocess
from collections import namedtuple
from ansible.module_utils.basic import *
DOCUMENTATION = '''
---
module: xrandr_facts
short_description: "gather facts about connected monitors and available modelines"
description:
- This module needs a running x-server on a given display in order to successfully call xrandr.
Returns the dictionary "xrandr", wich contains all screens with output states, connected displays,
EDID info and their modes and a recommendation for the best fitting tv mode.
options:
display:
required: False
default: ":0"
description:
- the DISPLAY variable to use when calling xrandr
preferred_outpus:
required: False
default: ["HDMI", "DP", "DVI", "VGA", "TV": 0]
description:
- ranking of the preferred display connectors
preferred_refreshrates:
required: False
default: ["50", "60", "75", "30", "25"]
description:
- ranking of the preferred display refreshrate
preferred_resolutions:
required: False
default: ["7680x4320", "3840x2160", "1920x1080", "1280x720", "720x576"]
description:
- ranking of the preferred display resolutions
write_edids:
required: False
default: True
description:
- write edid data to /etc/X11/edid.{connector}.bin
'''
EXAMPLES = '''
- name: "collect facts for connected displays"
action: xserver_facts
display: ":0"
- debug:
var: xrandr
- debug:
var: xorg
'''
ARG_SPECS = {
'display': dict(default=":0", type='str', required=False),
'preferred_outputs': dict(
default=["HDMI", "DP", "DVI", "VGA", "TV"], type='list', required=False),
'preferred_refreshrates': dict(
default=[50, 60, 75, 30, 25], type='list', required=False),
'preferred_resolutions': dict(
default=[
"7680x4320", "3840x2160", "1920x1080", "1280x720", "720x576"],
type='list', required=False),
'write_edids': dict(default=True, type='bool', required=False),
}
SCREEN_REGEX = re.compile("^(?P<screen>Screen\s\d+:)(?:.*)")
CONNECTOR_REGEX = re.compile(
"^(?P<connector>.*-\d+)\s(?P<connection_state>connected|disconnected)\s(?P<primary>primary)?")
MODE_REGEX = re.compile("^\s+(?P<resolution>\d{3,}x\d{3,}).*")
Mode = namedtuple('Mode', ['connection', 'resolution', 'refreshrate'])
def check_for_screen(line):
"""check line for screen information"""
match = re.match(SCREEN_REGEX, line)
if match:
return match.groupdict()['screen']
def check_for_connection(line):
"""check line for connection name and state"""
match = re.match(CONNECTOR_REGEX, line)
connector = None
is_connected = False
if match:
match = match.groupdict()
connector = match['connector']
is_connected = True if match['connection_state'] == 'connected' else False
return connector, is_connected
def get_indentation(line):
"""return the number of leading whitespace characters"""
return len(line) - len(line.lstrip())
def sort_mode(mode):
"""rate modes by several criteria"""
connection_score = 0
rrate_score = 0
resolution_score = 0
preferred_rrates = module.params['preferred_refreshrates']
# [50, 60]
preferred_resolutions = module.params['preferred_resolutions']
# ["7680x4320", "3840x2160", "1920x1080", "1280x720", "720x576"]
preferred_outputs = module.params['preferred_outputs']
# ["HDMI", "DP", "DVI", "VGA"]
if mode.refreshrate in preferred_rrates:
rrate_score = len(preferred_rrates) - preferred_rrates.index(mode.refreshrate)
if mode.resolution in preferred_resolutions:
resolution_score = len(preferred_resolutions) - preferred_resolutions.index(mode.resolution)
x_resolution, y_resolution = (int(n) for n in mode.resolution.split('x'))
connection = mode.connection.split('-')[0]
if connection in preferred_outputs:
connection_score = len(preferred_outputs) - preferred_outputs.index(connection)
return (rrate_score, resolution_score, x_resolution, y_resolution, connection_score)
def parse_xrandr_verbose(iterator):
"""parse the output of xrandr --verbose using an iterator delivering single lines"""
xorg = {}
is_connected = False
for line in iterator:
if line.startswith('Screen'):
screen = check_for_screen(line)
xorg[screen] = {}
elif 'connected' in line:
connector, is_connected = check_for_connection(line)
xorg[screen][connector] = {
'is_connected': is_connected,
'EDID': '',
'modes': {},
'preferred': '',
'current': '',
'auto': '',
}
elif is_connected and 'EDID:' in line:
edid_str = ""
outer_indentation = get_indentation(line)
while True:
line = next(iterator)
if get_indentation(line) > outer_indentation:
edid_str += line.strip()
else:
break
xorg[screen][connector]['EDID'] = edid_str
elif is_connected and "MHz" in line and not "Interlace" in line:
match = re.match(MODE_REGEX, line)
if match:
match = match.groupdict()
preferred = bool("+preferred" in line)
current = bool("*current" in line)
while True:
line = next(iterator)
if line.strip().startswith('v:'):
refresh_rate = ast.literal_eval(line.split()[-1][:-2])
rrate = int(round(refresh_rate))
if xorg[screen][connector]['modes'].get(match['resolution']) is None:
xorg[screen][connector]['modes'][match['resolution']] = []
if rrate not in xorg[screen][connector]['modes'][match['resolution']]:
xorg[screen][connector]['modes'][match['resolution']].append(rrate)
if preferred:
xorg[screen][connector]['preferred'] = "{}_{}".format(
match['resolution'], rrate)
if current:
xorg[screen][connector]['current'] = "{}_{}".format(
match['resolution'], rrate)
break
return xorg
def parse_edid_data(edid):
vendor = "Unknown"
model = "Unknown"
data = subprocess.check_output("parse-edid < {}".format(edid), shell=True, universal_newlines=True)
for line in data.splitlines():
if "VendorName" in line:
vendor = line.strip().split('"')[1]
if "ModelName" in line:
model = line.strip().split('"')[1]
return vendor, model
def collect_nvidia_data():
BusID_RE = re.compile((
'(?P<domain>[0-9a-fA-F]+)'
':'
'(?P<bus>[0-9a-fA-F]+)'
':'
'(?P<device>[0-9a-fA-F]+)'
'\.'
'(?P<function>[0-9a-fA-F]+)'
))
try:
data = subprocess.check_output(["nvidia-smi", "--query-gpu=name,pci.bus_id", "--format=csv", "-i0"],
universal_newlines=True)
except subprocess.CalledProcessError:
pass
else:
for row in csv.DictReader(data.splitlines(), delimiter=',', skipinitialspace=True):
name = row['name']
bus_id = row['pci.bus_id']
# pci.bus_id structure as reported by nvidia-smi: "domain:bus:device.function", in hex.
match = BusID_RE.search(bus_id)
if match:
domain, bus, device, function = (int(n, 16) for n in match.groups())
bus_id = "PCI:{:d}@{:d}:{:d}:{:d}".format(bus, domain, device, function)
return name, bus_id
raise ValueError
def output_data(data, write_edids=True):
if data:
modes = []
for _, screen_data in data.items():
for connector, connection_data in screen_data.items():
if connection_data.get('EDID') and write_edids:
with open('/etc/X11/edid.{}.bin'.format(connector), 'wb') as edid:
edid.write(binascii.a2b_hex(connection_data['EDID']))
for resolution, refreshrates in connection_data['modes'].items():
for refreshrate in refreshrates:
modes.append(Mode(connector, resolution, refreshrate))
if modes:
result = {}
try:
gpu_name, bus_id = collect_nvidia_data()
except ValueError:
gpu_name = None
bus_id = None
def create_entry(my_dict, name, connector, resolution, refreshrate, vendor, model):
my_dict[name] = {
'connector': connector,
'resolution': resolution,
'refreshrate': refreshrate,
'edid': '/etc/X11/edid.{}.bin'.format(connector),
'mode': "{}_{}".format(resolution, refreshrate),
'vendor': vendor,
'model': model,
}
if gpu_name and bus_id:
result[name]['gpu_name'] = gpu_name
result[name]['bus_id'] = bus_id
connector_0, resolution_0, refreshrate_0 = max(modes, key=sort_mode)[:3]
vendor_0, model_0 = parse_edid_data('/etc/X11/edid.{}.bin'.format(connector_0))
create_entry(result, 'primary', connector_0, resolution_0, refreshrate_0, vendor_0, model_0)
# check if additional monitors exist
other_modes = [mode for mode in modes if mode[0] != connector_0]
if other_modes:
connector_1, resolution_1, refreshrate_1 = max(other_modes, key=sort_mode)[:3]
vendor_1, model_1 = parse_edid_data('/etc/X11/edid.{}.bin'.format(connector_1))
create_entry(result, 'secondary', connector_1, resolution_1, refreshrate_1, vendor_1, model_1)
#print(json.dumps(data, sort_keys=True, indent=4))
module.exit_json(changed=True if write_edids else False, ansible_facts={'xrandr': data, 'xorg': result})
if __name__ == '__main__':
module = AnsibleModule(argument_spec=ARG_SPECS, supports_check_mode=False,)
try:
d = subprocess.check_output(['xrandr', '-d', module.params['display'], '--verbose'], universal_newlines=True).splitlines()
except subprocess.CalledProcessError:
xorg_data = {}
else:
xorg_data = parse_xrandr_verbose(iter(d))
output_data(xorg_data, module.params['write_edids'])
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
- name: Stop VDR
systemd:
name: vdr.service
state: stopped
enabled: yes
register: vdr_stop
- name: Start VDR
systemd:
name: vdr.service
state: started
enabled: yes
register: vdr_start
- name: Stop xlogin
systemd:
name: xlogin@vdr.service
state: stopped
enabled: yes
register: xlogin_stop
- name: Stop x
systemd:
name: x@vt7.service
state: stopped
register: x_stop