├── .gitignore
├── README.md
├── Vagrantfile
├── group_vars
├── download-servers
├── media-centers
├── rpi-dockers
└── rpis
├── hosts.dev
├── hosts.inc
├── playbook.yml
├── requirements.yml
├── roles
├── audio_center
│ ├── defaults
│ │ └── main.yml
│ ├── handlers
│ │ └── main.yml
│ ├── tasks
│ │ └── main.yml
│ └── templates
│ │ └── librespot
│ │ ├── librespot.service.j2
│ │ └── librespot.sh.j2
├── common
│ ├── defaults
│ │ └── main.yml
│ ├── handlers
│ │ └── main.yml
│ ├── tasks
│ │ ├── automount.yml
│ │ ├── base.yml
│ │ ├── fail2ban.yml
│ │ ├── firewall.yml
│ │ ├── hostname.yml
│ │ ├── logwatch.yml
│ │ ├── main.yml
│ │ ├── mosh.yml
│ │ ├── shared_group.yml
│ │ ├── ssh.yml
│ │ ├── ssmtp.yml
│ │ ├── sudoers_nopasswd.yml
│ │ ├── user_unsudo.yml
│ │ ├── vim.yml
│ │ ├── wifi.yml
│ │ └── zsh.yml
│ └── templates
│ │ ├── fail2ban
│ │ └── jail.local.j2
│ │ ├── security
│ │ └── apt_periodic
│ │ ├── ssh
│ │ └── banner.j2
│ │ ├── ssmtp
│ │ ├── revaliases.j2
│ │ └── ssmtp.conf.j2
│ │ └── wifi
│ │ └── wpa_supplicant.conf.j2
├── download_server
│ ├── defaults
│ │ └── main.yml
│ ├── handlers
│ │ └── main.yml
│ ├── tasks
│ │ └── main.yml
│ └── templates
│ │ └── aria2
│ │ ├── aria2.conf.j2
│ │ └── aria2.service
├── media_center
│ ├── defaults
│ │ └── main.yml
│ ├── handlers
│ │ └── main.yml
│ ├── tasks
│ │ ├── base.yml
│ │ ├── desktop.yml
│ │ ├── kodi.yml
│ │ ├── main.yml
│ │ └── tvheadend.yml
│ └── templates
│ │ ├── kodi
│ │ ├── advancedsettings.xml.j2
│ │ └── sources.xml.j2
│ │ └── system
│ │ ├── config.txt.j2
│ │ ├── input-rules
│ │ └── permissions-rules
└── rpi_docker
│ ├── defaults
│ └── main.yml
│ ├── handlers
│ └── main.yml
│ ├── tasks
│ └── main.yml
│ └── templates
│ └── docker
│ └── docker.service
└── variables.yml.inc
/.gitignore:
--------------------------------------------------------------------------------
1 | .vagrant
2 | /*.retry
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ansible-rpi 0.8.0
2 |
3 | ## Purpose
4 |
5 | Make Raspberry Pi up and running in a few command.
6 |
7 | Tested on a Rpi 3 B+ and a Rpi 1 B.
8 |
9 | ## Roles
10 |
11 | ### `common` role
12 |
13 | > Setup the Rpi with with updates and better security
14 |
15 | - Locale setup
16 | - System upgrade (*including kernel*)
17 | - Adding some useful packages (*curl, vim, tmux, git…*)
18 | - UFW firewall rules allowing user-specified ports and protocols
19 | - Logwatch for system status emails (*via SSMTP*)
20 | - SSH with key-only authentification
21 | - Custom sudo user for rpi (*thus disabling pi as Rpi sudoer*)
22 | - `oh-my-zsh` install and vim as default editor
23 | - Dynamic network folder and local drive setup (*Works with SAMBA and include basic credentials management*)
24 | - Fail2Ban configuration to send mail via SSMTP, handle a custom SSH port and
25 | some user-defined services
26 | - Optional hostname update and Zeroconf
27 | - Optional custom SSH banner
28 | - Optional Wifi config
29 | - Optional Mosh support
30 | - Optional unsudo of the pi user
31 | - Optionally add a list of user to the sudoers with NOPASSWD
32 |
33 | ### `download_server` role
34 |
35 | > Turn the Rpi in a download server for ddl and torrents
36 |
37 | - Aria2 daemon
38 | - RPC interface for remote monitoring with optional SSL encryption
39 | - Shared downloads directory (*may be replaced by a previously configured network folder*)
40 |
41 | ### `media_center` role
42 |
43 | > Turn your Raspberry into a decent customizable media center
44 |
45 | - Kodi basic installation with separate user
46 | - Dynamic sources creation (*may be linked to previously configured network folders*)
47 | - Buffer handling optimized for a Raspberry
48 | - Optional `kodi` user with `kodi-standalone` and a minimal Openbox setup
49 | - Optional [Tvheadend](https://tvheadend.org/) install with basic config
50 |
51 | ### `rpi_docker` role
52 |
53 | > Setup and enable control of a distant Raspberry Pi Docker host via Ansible
54 |
55 | - [HypriotOS](https://blog.hypriot.com/) oriented setup
56 | - Docker containers and deamon are behind the firewall by default (*see Docker Support for more infos*)
57 | - Ansible tools are setup (*allowing you to use docker_container, docker_image Ansible modules…*)
58 |
59 | ### `unbound` role
60 |
61 | > Transform your Raspberry Pi into a DNS Server
62 |
63 | - Unbound setup & configuration
64 | - Add DNS entries
65 | - Generation of DNS entries from ansible inventory (A entries and reverse)
66 | - Forward to another DNS
67 | - IPv4 only for reverse
68 |
69 | *Note: Source code is in a [separate role](https://github.com/davidderus/ansible-role-unbound).*
70 |
71 | ### `audio_center` role
72 |
73 | > Turns your Raspberry into a Spotify player
74 |
75 | **This role requires a PREMIUM Spotify account**
76 |
77 | - Setup an headless Spotify Connect client ([librespot](https://github.com/plietar/librespot))
78 | - Update audio config
79 |
80 | ## Setup
81 |
82 | ### With examples
83 |
84 | ```
85 | # First
86 | cp hosts.inc /etc/ansible/hosts
87 |
88 | # Then
89 | cp playbook.yml.inc playbook.yml
90 | cp variables.yml.inc /etc/ansible/host_vars/my-host.yml
91 | ```
92 |
93 | ### Usage
94 |
95 | First update the `hosts` file to target your Rpis.
96 |
97 | I recommend using an up-to-date [Raspbian Lite image](https://downloads.raspberrypi.org/raspbian_lite_latest).
98 |
99 | Make sure that the Rpi is SSHable (**latest raspbian lite images come with SSH
100 | disabled by default, creating a file with name "ssh" in boot partition is
101 | required to enable it.**).
102 |
103 | Then the first time run:
104 |
105 | ```shell
106 | ansible-playbook playbook.yml -u pi --ask-pass
107 | ```
108 |
109 | **You can also store user name in inventory file and user's pass in your Ansible
110 | vault.**
111 |
112 | ### Dev with Vagrant
113 |
114 | First run:
115 |
116 | ```shell
117 | ansible-playbook playbook.yml -i hosts.dev
118 | ```
119 |
120 | Next runs:
121 |
122 | ```shell
123 | # Editing the hosts file may be required to update the SSH port
124 | # A vagrant reload may also be needed
125 | # Checks access with
126 | ansible all -m ping -u neo
127 |
128 | # Execute updated playbook
129 | ansible-playbook playbook.yml -u neo --ask-become-pass
130 | ```
131 |
132 | **You can also store user name in inventory file and user's pass in your Ansible
133 | vault.**
134 |
135 | ## User password generation
136 |
137 | `password_hash` is a useful Jinja filter but uses 656000 rounds for SHA512 hashing.
138 |
139 | The default is 5000 in glibc [1], and it adds an important computing cost on a Rpi.
140 |
141 | This may cause axtra slowness on user authentification (*ie. sudo password prompt*)
142 |
143 | Please use the following command to generate a user password hash [2]:
144 |
145 | ```shell
146 | python -c "from passlib.hash import sha512_crypt; import getpass; print sha512_crypt.encrypt(getpass.getpass(), rounds=5000)"
147 | ```
148 |
149 | [1](https://github.com/ansible/ansible/issues/15326)
150 | [2](https://docs.ansible.com/ansible/faq.html#how-do-i-generate-crypted-passwords-for-the-user-module)
151 |
152 | ## Docker Support
153 |
154 | In order to ease Docker handling on Rpi, I recommend the
155 | [HypriotOS image](http://blog.hypriot.com/downloads/).
156 |
157 | ### Current state
158 |
159 | The `rpi_docker` role is tested with it, but may work with other setups.
160 |
161 | Modify the following vars in order to adapt to your device:
162 |
163 | ```yml
164 | rd_limit_nofile: 1048576
165 | rd_limit_nproc: 1048576
166 | rd_limit_core: infinity
167 | ```
168 |
169 | ### Security
170 |
171 | The `common` role will secure the HypriotOS Rpi in a way that by default:
172 |
173 | - `docker-machine create` will **fail**
174 | (_default user must have a NOPASSD sudo, see [](https://docs.docker.com/machine/drivers/generic/#/sudo-privileges)_)
175 | - Docker daemon tcp port (_2376_) will be unreachable (_however you can enable it manually in allowed_ports var_) but is started by default
176 | - Docker unix socket is accessible
177 |
178 | You may want to look to [this](https://github.com/DieterReuter/arm-docker-fixes/tree/master/001-fix-docker-machine-1.8.0-create-for-arm)
179 | for a manual `docker-machine` setup.
180 |
181 | Docker-machine and Raspbian Docker support may come in a future release.
182 |
183 | ### Defaults
184 |
185 | - `storage_driver` is `overlay`
186 | - The `tlsverify` flag is enabled, and `tlscacert`, `tlscert`, `tlskey`
187 | - `LimitNOFILE` and `LimitNPROC` are set, but `LimitCORE` is not
188 | - iptables addition by Docker are deactivated
189 |
190 | ## TODO
191 |
192 | - [] Sudoers file rewrite
193 | - [] Segmentation into roles
194 | - [] Contribution guidelines
195 | - [] …
196 |
--------------------------------------------------------------------------------
/Vagrantfile:
--------------------------------------------------------------------------------
1 | # -*- mode: ruby -*-
2 | # vi: set ft=ruby :
3 |
4 | # All Vagrant configuration is done below. The "2" in Vagrant.configure
5 | # configures the configuration version (we support older styles for
6 | # backwards compatibility). Please don't change it unless you know what
7 | # you're doing.
8 | Vagrant.configure(2) do |config|
9 | # The most common configuration options are documented and commented below.
10 | # For a complete reference, please see the online documentation at
11 | # https://docs.vagrantup.com.
12 |
13 | # Every Vagrant development environment requires a box. You can search for
14 | # boxes at https://atlas.hashicorp.com/search.
15 | config.vm.box = "debian/jessie64"
16 |
17 | # Disable automatic box update checking. If you disable this, then
18 | # boxes will only be checked for updates when the user runs
19 | # `vagrant box outdated`. This is not recommended.
20 | # config.vm.box_check_update = false
21 |
22 | # Create a forwarded port mapping which allows access to a specific port
23 | # within the machine from a port on the host machine. In the example below,
24 | # accessing "localhost:8080" will access port 80 on the guest machine.
25 | config.vm.network "forwarded_port", guest: 4480, host: 4480
26 | config.vm.network "forwarded_port", guest: 6800, host: 6800
27 | config.vm.network "forwarded_port", guest: 60001, host: 60001, protocol: 'udp'
28 | config.vm.network "forwarded_port", guest: 5353, host: 5353, protocol: 'udp'
29 |
30 | # Create a private network, which allows host-only access to the machine
31 | # using a specific IP.
32 | # config.vm.network "private_network", ip: "192.168.33.10"
33 |
34 | # Create a public network, which generally matched to bridged network.
35 | # Bridged networks make the machine appear as another physical device on
36 | # your network.
37 | # config.vm.network "public_network"
38 |
39 | # Share an additional folder to the guest VM. The first argument is
40 | # the path on the host to the actual folder. The second argument is
41 | # the path on the guest to mount the folder. And the optional third
42 | # argument is a set of non-required options.
43 | # config.vm.synced_folder "../data", "/vagrant_data"
44 |
45 | # Provider-specific configuration so you can fine-tune various
46 | # backing providers for Vagrant. These expose provider-specific options.
47 | # Example for VirtualBox:
48 | #
49 | # config.vm.provider "virtualbox" do |vb|
50 | # # Display the VirtualBox GUI when booting the machine
51 | # vb.gui = true
52 | #
53 | # # Customize the amount of memory on the VM:
54 | # vb.memory = "1024"
55 | # end
56 | #
57 | # View the documentation for the provider you are using for more
58 | # information on available options.
59 |
60 | # Define a Vagrant Push strategy for pushing to Atlas. Other push strategies
61 | # such as FTP and Heroku are also available. See the documentation at
62 | # https://docs.vagrantup.com/v2/push/atlas.html for more information.
63 | # config.push.define "atlas" do |push|
64 | # push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME"
65 | # end
66 |
67 | # Enable provisioning with a shell script. Additional provisioners such as
68 | # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
69 | # documentation for more information about their specific syntax and use.
70 | # config.vm.provision "shell", inline: <<-SHELL
71 | # sudo apt-get update
72 | # sudo apt-get install -y apache2
73 | # SHELL
74 | end
75 |
--------------------------------------------------------------------------------
/group_vars/download-servers:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | ds_shared_group: "{{ server_shared_group }}"
4 |
--------------------------------------------------------------------------------
/group_vars/media-centers:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | mc_shared_group: "{{ server_shared_group }}"
4 | mc_user_name: "{{ server_user_name }}"
5 |
--------------------------------------------------------------------------------
/group_vars/rpi-dockers:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | # Needed for HypriotOS
4 | with_custom_hostname: False
5 |
--------------------------------------------------------------------------------
/group_vars/rpis:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | # We're updating server variables for rpis
4 |
5 | server_host_name: "rpi-{{ server_user_name }}"
6 | server_shared_group: "rpi-{{ server_user_name }}-shared"
7 |
8 | server_user_groups: "{{ server_shared_group }},pi,adm,dialout,cdrom,sudo,audio,video,plugdev,games,users,input,netdev,gpio,i2c,spi,tty"
9 |
--------------------------------------------------------------------------------
/hosts.dev:
--------------------------------------------------------------------------------
1 | [download-servers]
2 | vagrant ansible_host=127.0.0.1 ansible_user=vagrant ansible_port=2222 ansible_ssh_private_key_file=./.vagrant/machines/default/virtualbox/private_key
3 |
--------------------------------------------------------------------------------
/hosts.inc:
--------------------------------------------------------------------------------
1 | [servers:children]
2 | rpis
3 |
4 | [rpis]
5 | 127.0.0.1
6 |
7 | [download-servers]
8 | 127.0.0.1
9 |
--------------------------------------------------------------------------------
/playbook.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Configure and securise any Debian server
4 |
5 | hosts: servers
6 | roles:
7 | - common
8 |
9 | become: yes
10 |
11 | - name: Setup and manage a download server
12 |
13 | hosts: download-servers
14 | roles:
15 | - download_server
16 |
17 | become: yes
18 |
19 | - name: Setup a media center
20 |
21 | hosts: media-centers
22 | roles:
23 | - media_center
24 |
25 | become: yes
26 |
27 | - name: Setup a Docker host
28 |
29 | hosts: rpi-dockers
30 | roles:
31 | - rpi_docker
32 |
33 | become: yes
34 |
35 | - name: Setup a DNS
36 |
37 | hosts: dns-servers
38 | roles:
39 | - unbound-role
40 |
41 | become: yes
42 |
43 | - name: Setup an Audio Server
44 |
45 | hosts: audio-centers
46 | roles:
47 | - audio_center
48 |
49 | become: yes
50 |
--------------------------------------------------------------------------------
/requirements.yml:
--------------------------------------------------------------------------------
1 | - src: https://github.com/davidderus/ansible-role-unbound.git
2 | name: unbound-role
3 |
--------------------------------------------------------------------------------
/roles/audio_center/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | ac_respot_output_dir: /usr/local/lib
4 | ac_respot_binary: "{{ ac_respot_output_dir }}/librespot"
5 | ac_spotify_device_name: "{{ inventory_hostname }}"
6 | ac_respot_bitrate: 320
7 | ac_respot_device: hw:CARD=ALSA,DEV=0
8 | ac_respot_backend: alsa
9 | ac_respot_user_dir: '/etc/librespot'
10 | ac_respot_cache_dir: '{{ ac_respot_user_dir }}/cache'
11 |
12 | ac_required_packages:
13 | - pulseaudio
14 | - alsa-utils
15 |
--------------------------------------------------------------------------------
/roles/audio_center/handlers/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Restart librespot
4 | service: name=librespot state=restarted
5 |
--------------------------------------------------------------------------------
/roles/audio_center/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Install librespot required packages
4 | apt:
5 | name: '{{ item }}'
6 | state: latest
7 | with_items: '{{ ac_required_packages }}'
8 |
9 | - name: Set default output to analog (this does not create a conflict with Kodi)
10 | command: 'amixer -c 0 cset numid=3 1'
11 |
12 | - name: Set volume to 100%
13 | command: "amixer sset 'Master' 100%"
14 | become: false
15 |
16 | - name: 'Download armv7hf binary (for Rpi3) to {{ ac_respot_output_dir }}'
17 | get_url:
18 | url: '{{ ac_respot_url }}'
19 | dest: '{{ ac_respot_binary }}'
20 | checksum: 'sha256:{{ ac_respot_checksum }}'
21 |
22 | - name: Make binary executable
23 | file:
24 | path: '{{ ac_respot_binary }}'
25 | owner: root
26 | group: root
27 | mode: 'u=rwx,g=rx,o=rx'
28 |
29 | - name: Build librespot directory
30 | file:
31 | path: '{{ ac_respot_user_dir }}'
32 | owner: root
33 | group: root
34 | mode: 'u=rw,g=r,o=r'
35 | state: directory
36 |
37 | - name: Build cache directory
38 | file:
39 | path: '{{ ac_respot_cache_dir }}'
40 | owner: root
41 | group: root
42 | mode: 'u=rw,g=r,o=r'
43 | state: directory
44 |
45 | - name: Install librespot script
46 | template:
47 | src: librespot/librespot.sh.j2
48 | dest: '{{ ac_respot_user_dir }}/librespot.sh'
49 | owner: root
50 | group: root
51 | mode: 0700
52 |
53 | - name: Install librespot service
54 | template:
55 | src: librespot/librespot.service.j2
56 | dest: /etc/systemd/system/librespot.service
57 | owner: root
58 | group: root
59 | mode: 0644
60 |
61 | - name: Enable librespot on boot
62 | service: name=librespot enabled=true state=restarted
63 |
--------------------------------------------------------------------------------
/roles/audio_center/templates/librespot/librespot.service.j2:
--------------------------------------------------------------------------------
1 | # {{ ansible_managed }}
2 |
3 | [Unit]
4 | Description=librespot daemon
5 | After=network.target
6 |
7 | [Service]
8 | Type=simple
9 | ExecStart={{ ac_respot_user_dir }}/librespot.sh
10 | Restart=on-failure
11 | RestartSec=10
12 |
13 | [Install]
14 | WantedBy=multi-user.target
15 |
--------------------------------------------------------------------------------
/roles/audio_center/templates/librespot/librespot.sh.j2:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # {{ ansible_managed }}
4 |
5 | # I'm not a very big fan of the Spotify password in a readable script launching
6 | # a world readable command. But that seems to be the only way for now
7 | # (see https://github.com/plietar/librespot/pull/24)
8 |
9 | {{ ac_respot_binary }} --username {{ ac_spotify_user }} \
10 | --password {{ ac_spotify_password }} \
11 | --name {{ ac_spotify_device_name }} \
12 | --bitrate {{ ac_respot_bitrate }} \
13 | --cache {{ ac_respot_cache_dir }} \
14 | --device {{ ac_respot_device }} \
15 | --backend {{ ac_respot_backend }}
16 |
--------------------------------------------------------------------------------
/roles/common/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | # Locales
4 | server_locales:
5 | - en_US.UTF-8
6 |
7 | # Packages
8 | required_packages:
9 | - ufw
10 | - fail2ban # Using defaults
11 | - unattended-upgrades
12 | - logwatch # Using defaults
13 | - rsync
14 | - htop
15 | - curl
16 | - git
17 | - tmux
18 | - tree
19 |
20 | ssh_custom_banner: False
21 |
22 | with_wifi: False
23 | with_vim: False
24 | with_zsh: False
25 | with_mosh: False
26 | with_automount: False
27 | with_custom_hostname: False
28 |
29 | automount_local_devices: []
30 | automount_network_folders: []
31 | automount_group: "{{ server_shared_group|default('root') }}"
32 |
33 | ssmtp_mailhub: smtp.gmail.com:587
34 | ssmtp_auth_user: "{{ ssmtp_email }}"
35 |
36 | server_allow_upgrade: true
37 | server_allow_reboot: true
38 |
39 | server_fail2ban_jail_file: /etc/fail2ban/jail.conf
40 | server_fail2ban_jail_local_file: /etc/fail2ban/jail.local
41 | server_fail2ban_services: []
42 | server_fail2ban_email: '{{ ssmtp_email }}'
43 |
--------------------------------------------------------------------------------
/roles/common/handlers/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Restart ssh
4 | service: name=ssh state=restarted
5 |
6 | - name: Reload firewall
7 | ufw: state=reloaded
8 |
9 | - name: Restart fail2ban
10 | service:
11 | name: fail2ban
12 | state: restarted
13 |
--------------------------------------------------------------------------------
/roles/common/tasks/automount.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Install automount packages
4 | apt: state=installed pkg={{ item }}
5 | with_items:
6 | - samba
7 | - cifs-utils
8 |
9 | - name: Create local devices folders
10 | file: dest=/mnt/{{ item.name }} state=directory mode=0770 group={{ automount_group }}
11 | with_items: "{{ automount_local_devices }}"
12 | ignore_errors: yes # Ignore group errors on already mounted directories (next step will fail if its another error)
13 |
14 | - name: Mount local devices by label
15 | mount:
16 | name: "/mnt/{{ item.name }}"
17 | src: "UUID={{ item.uuid }}"
18 | fstype: "{{ item.type }}"
19 | opts: "auto,nofail,noatime,rw"
20 | state: mounted
21 | with_items: "{{ automount_local_devices }}"
22 |
23 | - name: "Create remote folders for group {{ automount_group }}"
24 | file: dest=/media/{{ item.name }} state=directory mode=0770 group={{ automount_group }}
25 | with_items: "{{ automount_network_folders }}"
26 | ignore_errors: yes # Ignore group errors on already mounted directories (next step will fail if its another error)
27 |
28 | - name: Mount remote folders
29 | mount:
30 | name: "/media/{{ item.name }}"
31 | src: "{{ item.network_folder }}"
32 | fstype: "{{ item.type|default('cifs') }}"
33 | opts: "domain={{ item.domain }},username={{ item.user }},password={{ item.password }},file_mode=0770,dir_mode=0770,noperm,iocharset=utf8,_netdev"
34 | state: mounted
35 | with_items: "{{ automount_network_folders }}"
36 |
--------------------------------------------------------------------------------
/roles/common/tasks/base.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Installing aptitude
4 | apt:
5 | name: aptitude
6 | state: latest
7 |
8 | - name: Update APT package cache
9 | apt: update_cache=yes cache_valid_time=3600
10 |
11 | - name: Upgrading distribution
12 | apt: upgrade=yes
13 | register: upgrade_distribution
14 | when: server_allow_upgrade
15 |
16 | - name: Rebooting server
17 | shell: sleep 2 && shutdown -r now "Ansible updates triggered"
18 | async: 1
19 | poll: 0
20 | ignore_errors: true
21 | when:
22 | - upgrade_distribution|changed
23 | - server_allow_reboot
24 |
25 | - name: Waiting for server to come back
26 | local_action: wait_for host={{ inventory_hostname }} state=started delay=30 timeout=300
27 | become: false
28 | when:
29 | - upgrade_distribution|changed
30 | - server_allow_reboot
31 |
32 | - name: Setup locale
33 | locale_gen: name={{item}} state=present
34 | with_items: "{{ server_locales }}"
35 | register: update_locale
36 |
37 | - name: Generate locales
38 | command: locale-gen
39 | when: update_locale|changed
40 |
41 | - name: Install required packages
42 | apt: name={{ item }} state=latest
43 | with_items: "{{ required_packages }}"
44 |
45 | - name: Create new server owner
46 | user: name={{ server_user_name }}
47 | groups={{ server_user_groups }}
48 | password={{ server_user_password_hash }}
49 | shell=/bin/bash
50 | append=yes
51 | state=present
52 |
53 | - name: Adjust APT update intervals
54 | template: src=security/apt_periodic dest=/etc/apt/apt.conf.d/10periodic owner=root group=root mode=0644
55 |
--------------------------------------------------------------------------------
/roles/common/tasks/fail2ban.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Update ssh port in config
4 | replace:
5 | path: '{{ server_fail2ban_jail_file }}'
6 | regexp: '^port\s+=\s+ssh$'
7 | replace: 'port = {{ ssh_port }}'
8 | notify: Restart fail2ban
9 | when: ssh_port is defined and ssh_port != 22
10 |
11 | - name: Update email in config
12 | lineinfile:
13 | path: '{{ server_fail2ban_jail_file }}'
14 | regexp: '^destemail\s+='
15 | line: 'destemail = {{ server_fail2ban_email }}'
16 | state: present
17 | notify: Restart fail2ban
18 |
19 | - name: Send more than an ip on jailed
20 | lineinfile:
21 | path: '{{ server_fail2ban_jail_file }}'
22 | regexp: '^action\s+=\s+\%\(action_\w+\)s'
23 | line: 'action = %(action_mwl)s'
24 | state: present
25 | notify: Restart fail2ban
26 |
27 | - name: Generate jail.local file
28 | template:
29 | src: fail2ban/jail.local.j2
30 | dest: '{{ server_fail2ban_jail_local_file }}'
31 | owner: root
32 | group: root
33 | mode: 0644
34 | notify: Restart fail2ban
35 |
--------------------------------------------------------------------------------
/roles/common/tasks/firewall.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Allow specific ports connections
4 | ufw: rule=allow port={{ item.port }} proto={{ item.proto }}
5 | with_items:
6 | - "{{ allowed_ports }}"
7 |
8 | - name: Enable firewall
9 | ufw: state=enabled policy=deny
10 |
--------------------------------------------------------------------------------
/roles/common/tasks/hostname.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Updating hostname (1/2)
4 | hostname: name={{ server_host_name }}
5 | register: hostname_change
6 |
7 | # A second definitive update is needed on certain Rpis
8 | - name: Updating hostname (2/2)
9 | copy: content="{{inventory_hostname_short}}{{'\n'}}"
10 | dest=/etc/hostname
11 | backup=yes
12 | register: hostname_change
13 |
14 | - name: Update /etc/hosts
15 | lineinfile: dest=/etc/hosts
16 | regexp="^127\.0\.1\.1"
17 | line="127.0.1.1{{'\t'}}{{ server_host_name }}"
18 | backup=yes
19 | state=present
20 |
21 | - name: Install Zeroconf utilities
22 | apt: name={{ item }} state=latest
23 | with_items:
24 | - libnss-mdns
25 | - avahi-daemon
26 |
27 | - name: Avahi Zeroconf Daemon ports
28 | ufw: rule=allow port=5353 proto=udp
29 | notify: Reload firewall
30 |
31 | - name: Reload facts
32 | setup:
33 | when: hostname_change|changed
34 |
35 | - name: Rebooting server
36 | shell: sleep 2 && shutdown -r now "Ansible updates triggered"
37 | async: 1
38 | poll: 0
39 | ignore_errors: true
40 | when:
41 | - hostname_change|changed
42 | - server_allow_reboot
43 |
44 | - name: Waiting for server to come back
45 | local_action: wait_for host={{ inventory_hostname }} state=started delay=30 timeout=300
46 | become: false
47 | when:
48 | - hostname_change|changed
49 | - server_allow_reboot
50 |
--------------------------------------------------------------------------------
/roles/common/tasks/logwatch.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Email logwatch summary daily
4 | lineinfile: dest=/etc/cron.daily/00logwatch
5 | regexp="^/usr/sbin/logwatch"
6 | line="/usr/sbin/logwatch --output mail --mailto {{ logwatch_email }} --detail high"
7 | state=present create=yes
8 |
--------------------------------------------------------------------------------
/roles/common/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | # Required
4 |
5 | - include: base.yml
6 | - include: ssh.yml
7 |
8 | - include: hostname.yml
9 | when: with_custom_hostname
10 |
11 | - include: ssmtp.yml
12 |
13 | - include: fail2ban.yml
14 | tags: fail2ban
15 |
16 | - include: firewall.yml
17 |
18 | - include: logwatch.yml
19 |
20 | # Optionnals
21 |
22 | - include: wifi.yml
23 | when: with_wifi
24 | - include: vim.yml
25 | when: with_vim
26 | - include: mosh.yml
27 | when: with_mosh
28 | - include: shared_group.yml
29 | when: server_shared_group is defined
30 | - include: automount.yml
31 | when: with_automount
32 | - include: zsh.yml
33 | when: with_zsh
34 |
35 | # Super optionnal (may break current process if you're using the user)
36 |
37 | - include: user_unsudo.yml server_unsudo_user={{ item }}
38 | with_items: "{{ server_unsudoed_users }}"
39 | when: server_unsudoed_users is defined
40 |
41 | - include: sudoers_nopasswd.yml
42 | with_items: "{{ server_sudo_nopasswd_users }}"
43 | loop_control:
44 | loop_var: nopasswd_user
45 | when: server_sudo_nopasswd_users is defined
46 |
--------------------------------------------------------------------------------
/roles/common/tasks/mosh.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Install Mosh
4 | apt: pkg=mosh state=present
5 |
6 | - name: Allow Mosh ports
7 | ufw: rule=allow port=60000:61000 proto=udp
8 | notify: Reload firewall
9 |
10 | - debug: msg="Remember to use ssh port {{ ssh_port }} in order to establish a mosh connection"
11 | when: ssh_port is defined
12 |
--------------------------------------------------------------------------------
/roles/common/tasks/shared_group.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Add new group for shared items between roles
4 | group: state=present name={{ server_shared_group }}
5 |
--------------------------------------------------------------------------------
/roles/common/tasks/ssh.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Add authorized_keys for the user
4 | authorized_key: user={{ server_user_name }} key="{{ lookup('file', item) }}"
5 | with_items:
6 | - "{{ ssh_public_keys }}"
7 |
8 | - name: Change the SSH port
9 | lineinfile: dest=/etc/ssh/sshd_config
10 | regexp="^Port\s"
11 | line="Port {{ ssh_port }}"
12 | state=present
13 | notify: Restart ssh
14 | when: ssh_port is defined
15 |
16 | - name: Disallow root SSH access
17 | lineinfile: dest=/etc/ssh/sshd_config
18 | regexp="^PermitRootLogin"
19 | line="PermitRootLogin no"
20 | state=present
21 | notify: Restart ssh
22 |
23 | - name: Disallow password authentication
24 | lineinfile: dest=/etc/ssh/sshd_config
25 | regexp="^PasswordAuthentication"
26 | line="PasswordAuthentication no"
27 | state=present
28 | notify: Restart ssh
29 |
30 | - name: Add SSH banner
31 | template: src=ssh/banner.j2 dest=/etc/issue.net owner=root group=root mode=0644
32 | when: ssh_custom_banner
33 |
34 | - name: Enable SSH banner
35 | lineinfile: dest=/etc/ssh/sshd_config
36 | regexp="^#?Banner"
37 | line="Banner /etc/issue.net"
38 | state=present
39 | when: ssh_custom_banner
40 | notify: Restart ssh
41 |
--------------------------------------------------------------------------------
/roles/common/tasks/ssmtp.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Install SSMTP
4 | apt: name=ssmtp state=installed
5 |
6 | - name: Set up ssmtp.conf
7 | template: src=ssmtp/ssmtp.conf.j2 dest=/etc/ssmtp/ssmtp.conf
8 |
9 | - name: Set up revaliases
10 | template: src=ssmtp/revaliases.j2 dest=/etc/ssmtp/revaliases
11 |
--------------------------------------------------------------------------------
/roles/common/tasks/sudoers_nopasswd.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: "Add {{ nopasswd_user }} to sudoers with the NOPASSWD option"
4 | lineinfile:
5 | path: /etc/sudoers
6 | state: present
7 | regexp: "^%{{ nopasswd_user }} ALL="
8 | line: "%{{ nopasswd_user }} ALL=(ALL) NOPASSWD: ALL"
9 | validate: '/usr/sbin/visudo -cf %s'
10 |
--------------------------------------------------------------------------------
/roles/common/tasks/user_unsudo.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: "Remove {{ server_unsudo_user }} user from sudoers"
4 | lineinfile: dest=/etc/sudoers regexp="^{{ server_unsudo_user }} ALL" state=absent backup=yes
5 |
6 | - name: "Limit {{ server_unsudo_user }}'s groups"
7 | user: name={{ server_unsudo_user }} state=present groups={{ server_unsudo_user }}
8 |
--------------------------------------------------------------------------------
/roles/common/tasks/vim.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Install Vim
4 | apt: pkg=vim state=present
5 |
6 | - name: Set Vim as default editor
7 | alternatives: "name={{ item }} path=/usr/bin/vim.basic"
8 | with_items:
9 | - editor
10 | - ex
11 | - rview
12 | - rvim
13 | - vi
14 | - view
15 | - vim
16 | - vimdiff
17 |
--------------------------------------------------------------------------------
/roles/common/tasks/wifi.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Configure WIFI
4 | template: src=wifi/wpa_supplicant.conf.j2 dest=/etc/wpa_supplicant/wpa_supplicant.conf mode=0600
5 |
--------------------------------------------------------------------------------
/roles/common/tasks/zsh.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Install ZSH
4 | apt: name=zsh state=present
5 |
6 | - name: Clone oh-my-zsh repo
7 | git: repo=https://github.com/robbyrussell/oh-my-zsh.git dest=/home/{{ server_user_name }}/.oh-my-zsh
8 |
9 | - name: Updates oh-my-zsh directory permissions
10 | file:
11 | path: '/home/{{ server_user_name }}/.oh-my-zsh'
12 | owner: '{{ server_user_name }}'
13 | group: '{{ server_user_name }}'
14 | mode: 0755
15 |
16 | - name: Updates oh-my-zsh directory content permissions
17 | file:
18 | path: '/home/{{ server_user_name }}/.oh-my-zsh'
19 | owner: '{{ server_user_name }}'
20 | group: '{{ server_user_name }}'
21 | recurse: 'yes'
22 |
23 | - name: Setup default zshrc
24 | copy: remote_src=true src=/home/{{ server_user_name }}/.oh-my-zsh/templates/zshrc.zsh-template dest=/home/{{ server_user_name }}/.zshrc backup=yes mode=0644 owner={{ server_user_name }} group={{ server_user_name }}
25 |
26 | - name: Change zsh theme
27 | lineinfile: dest=/home/{{ server_user_name }}/.zshrc regexp="^ZSH_THEME" line="ZSH_THEME=\"{{ server_zsh_theme|default('junkfood') }}\"" state=present
28 |
29 | - name: Update user default shell
30 | user: name={{ server_user_name }}
31 | shell=/bin/zsh
32 | state=present
33 |
--------------------------------------------------------------------------------
/roles/common/templates/fail2ban/jail.local.j2:
--------------------------------------------------------------------------------
1 | # {{ ansible_managed }}
2 |
3 | {% for service in server_fail2ban_services %}
4 | [{{ service.name }}]
5 |
6 | enabled = {{ '"%s"' | format(service.enabled | lower) }}
7 | port = {{ service.port }}
8 | filter = {{ service.filter }}
9 | logpath = {{ service.logpath }}
10 | {% if service.maxretry is defined %}
11 | maxretry = {{ service.maxretry }}
12 | {% endif %}
13 | {% if service.protocol is defined %}
14 | protocol = {{ service.protocol }}
15 | {% endif %}
16 | {% if service.action is defined %}
17 | action = %({{ service.action }})s
18 | {% endif %}
19 | {% if service.banaction is defined %}
20 | banaction = {{ service.banaction }}
21 | {% endif %}
22 |
23 | {% endfor %}
24 |
--------------------------------------------------------------------------------
/roles/common/templates/security/apt_periodic:
--------------------------------------------------------------------------------
1 | APT::Periodic::Update-Package-Lists "1";
2 | APT::Periodic::Download-Upgradeable-Packages "1";
3 | APT::Periodic::AutocleanInterval "7";
4 | APT::Periodic::Unattended-Upgrade "1";
5 |
--------------------------------------------------------------------------------
/roles/common/templates/ssh/banner.j2:
--------------------------------------------------------------------------------
1 | #########################{{ '#' * server_host_name|length }}
2 | # You are connected to {{ server_host_name }} #
3 | #########################{{ '#' * server_host_name|length }}
4 |
--------------------------------------------------------------------------------
/roles/common/templates/ssmtp/revaliases.j2:
--------------------------------------------------------------------------------
1 | root:{{ ssmtp_email }}:{{ ssmtp_mailhub }}
2 | {{ server_user_name }}:{{ ssmtp_email }}:{{ ssmtp_mailhub }}
3 |
--------------------------------------------------------------------------------
/roles/common/templates/ssmtp/ssmtp.conf.j2:
--------------------------------------------------------------------------------
1 | # Basic Config
2 | root={{ ssmtp_email }}
3 | AuthMethod=LOGIN
4 | UseSTARTTLS=YES
5 | hostname={{ server_host_name }}
6 | FromLineOverride=YES
7 |
8 | # Gmail Auth
9 | AuthUser={{ ssmtp_auth_user }}
10 | mailhub={{ ssmtp_mailhub }}
11 | AuthPass={{ ssmtp_auth_pass }}
12 |
--------------------------------------------------------------------------------
/roles/common/templates/wifi/wpa_supplicant.conf.j2:
--------------------------------------------------------------------------------
1 | # {{ ansible_managed }}
2 |
3 | ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
4 | update_config=1
5 | network={
6 | ssid="{{ wifi_ssid }}"
7 | psk="{{ wifi_password }}"
8 |
9 | # Protocol type can be: RSN (for WPA2) and WPA (for WPA1)
10 | proto={{ wifi_proto }}
11 |
12 | # Key management type can be: WPA-PSK or WPA-EAP (Pre-Shared or Enterprise)
13 | key_mgmt={{ wifi_key }}
14 |
15 | # Pairwise can be CCMP or TKIP (for WPA2 or WPA1)
16 | pairwise={{ wifi_pairwise }}
17 |
18 | #Authorization option should be OPEN for both WPA1/WPA2 (in less commonly used are SHARED and LEAP)
19 | auth_alg=OPEN
20 | }
21 |
--------------------------------------------------------------------------------
/roles/download_server/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | ds_user_name: download_server
4 | ds_downloads_root: "/home/{{ ds_user_name }}"
5 | ds_downloads_directory: "{{ ds_downloads_root }}/aria-downloads"
6 |
7 | aria2_rpc_enabled: False
8 | aria2_rpc_port: 6800
9 |
10 | aria2_rpc_encrypted: False
11 | aria2_cert_path: /home/{{ ds_user_name }}/.aria2/certificate.crt
12 | aria2_private_key_path: /home/{{ ds_user_name }}/.aria2/private_key.key
13 |
--------------------------------------------------------------------------------
/roles/download_server/handlers/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Restart aria2
4 | service: name=aria2 state=restarted
5 |
--------------------------------------------------------------------------------
/roles/download_server/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Install aria2 package
4 | apt: state=installed pkg=aria2
5 |
6 | - name: Add new user for download server
7 | user: name={{ ds_user_name }} groups={{ ds_shared_group }} comment="Aria2 Download Server" append=yes state=present
8 |
9 | - name: Make downloads directory
10 | file: path={{ ds_downloads_directory }} state=directory mode=0770
11 |
12 | - name: Make configuration directory
13 | file: path=/home/{{ ds_user_name }}/.aria2 state=directory owner={{ ds_user_name }} group={{ ds_user_name }}
14 |
15 | - name: Copy aria2 configuration
16 | template: src=aria2/aria2.conf.j2 dest=/home/{{ ds_user_name }}/.aria2/aria2.conf owner={{ ds_user_name }} group={{ ds_user_name }} mode=0600
17 | register: config_updated
18 |
19 | - name: Generate a certificate and private key for RPC communications
20 | command: openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout {{ aria2_private_key_path }} -out {{ aria2_cert_path }} -subj {{ aria2_cert_subject }}
21 | when:
22 | - aria2_rpc_encrypted
23 | - config_updated|changed
24 |
25 | - name: Updates private key permissions
26 | file: path={{ aria2_private_key_path }} owner={{ ds_user_name }} group={{ ds_user_name }} mode=0700
27 | when:
28 | - aria2_rpc_encrypted
29 | - config_updated|changed
30 |
31 | - name: Updates certificate permissions
32 | file: path={{ aria2_cert_path }} owner={{ ds_user_name }} group={{ ds_user_name }}
33 | when:
34 | - aria2_rpc_encrypted
35 | - config_updated|changed
36 |
37 | - fetch:
38 | src: "{{ aria2_cert_path }}"
39 | dest: tmp/aria2-{{ inventory_hostname }}.crt
40 | flat: true
41 | when:
42 | - aria2_rpc_encrypted
43 | - config_updated|changed
44 |
45 | - debug: msg="The certificate was copied in tmp/aria2-{{ inventory_hostname }}.crt"
46 | notify: Restart aria2
47 | when:
48 | - aria2_rpc_encrypted
49 | - config_updated|changed
50 |
51 | - name: Install aria2 service
52 | template: src=aria2/aria2.service dest=/etc/systemd/system/aria2.service owner=root group=root mode=0644
53 | notify: Restart aria2
54 |
55 | - name: Enable aria2 daemon on boot
56 | service: name=aria2 enabled=true state=restarted
57 |
--------------------------------------------------------------------------------
/roles/download_server/templates/aria2/aria2.conf.j2:
--------------------------------------------------------------------------------
1 | # {{ ansible_managed }}
2 |
3 | daemon=true
4 | continue=true
5 |
6 | split=24
7 | check-integrity=true
8 |
9 | max-concurrent-downloads=3
10 | max-connection-per-server=8
11 | max-file-not-found=3
12 | max-tries=5
13 |
14 | retry-wait=60
15 |
16 | dir={{ ds_downloads_directory }}
17 |
18 | enable-rpc=true
19 | rpc-allow-origin-all=true
20 | rpc-listen-all=true
21 | rpc-listen-port={{ aria2_rpc_port }}
22 | rpc-secret={{ aria2_rpc_token }}
23 |
24 | {% if aria2_rpc_encrypted %}
25 | rpc-secure=true
26 | rpc-certificate={{ aria2_cert_path }}
27 | rpc-private-key={{ aria2_private_key_path }}
28 | {% endif %}
29 |
--------------------------------------------------------------------------------
/roles/download_server/templates/aria2/aria2.service:
--------------------------------------------------------------------------------
1 | # {{ ansible_managed }}
2 |
3 | [Unit]
4 | Description=Aria2 Download Manager Daemon
5 | After=network.target
6 |
7 | [Service]
8 | Type=forking
9 | ExecStart=/usr/bin/aria2c --conf-path=/home/{{ ds_user_name }}/.aria2/aria2.conf
10 | User={{ ds_user_name }}
11 | Group={{ ds_shared_group }}
12 | WorkingDirectory={{ ds_downloads_directory }}
13 |
14 | [Install]
15 | WantedBy=multi-user.target
16 |
--------------------------------------------------------------------------------
/roles/media_center/defaults/main.yml:
--------------------------------------------------------------------------------
1 | kodi_apt_repo: "deb http://pipplware.pplware.pt/pipplware/dists/jessie/main/binary /"
2 | kodi_tvheadend_apt_repo: "deb https://dl.bintray.com/tvheadend/deb {{ ansible_distribution_release }} release"
3 |
4 | openbox_config_dir: /home/{{ mc_user_name }}/.config/openbox
5 |
6 | # Arrays of filepaths
7 | # Inspired by https://github.com/cmprescott/ansible-role-kodi
8 | kodi_source_files: []
9 | kodi_source_music: []
10 | kodi_source_pictures: []
11 | kodi_source_programs: []
12 | kodi_source_videos: []
13 |
14 | with_kodi_buffering: False
15 | kodi_read_buffer_factor: 20
16 | kodi_buffer_size: 139460608
17 |
18 | kodi_username: kodi
19 | kodi_user_dir: "/home/{{ kodi_username }}/.kodi"
20 |
21 | kodi_allow_reboot: true
22 |
23 | kodi_with_tvheadend: false
24 | kodi_tvheadend_username: admin
25 | kodi_tvheadend_password: admin
26 |
--------------------------------------------------------------------------------
/roles/media_center/handlers/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Restart kodi
4 | service: name=kodi state=restarted
5 |
--------------------------------------------------------------------------------
/roles/media_center/tasks/base.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | # See https://www.raspberrypi.org/documentation/configuration/config-txt.md
4 | - name: Setup custom config.txt
5 | template: src=system/config.txt.j2 dest=/boot/config.txt owner=root group=root mode=0755
6 | register: update_config
7 |
8 | - name: Create 99-input.rules
9 | template: src=system/input-rules dest=/etc/udev/rules.d/99-input.rules owner=root group=root mode=0644
10 |
11 | - name: Create 10-permissions.rules
12 | template: src=system/permissions-rules dest=/etc/udev/rules.d/10-permissions.rules owner=root group=root mode=0644
13 |
14 | - name: Rebooting Rpi
15 | shell: sleep 2 && shutdown -r now "Ansible updates triggered"
16 | async: 1
17 | poll: 0
18 | ignore_errors: true
19 | when:
20 | - update_config|changed
21 | - kodi_allow_reboot
22 |
23 | - name: Waiting for server to come back
24 | local_action: wait_for host={{ inventory_hostname }} state=started delay=30 timeout=300
25 | become: false
26 | when:
27 | - update_config|changed
28 | - kodi_allow_reboot
29 |
--------------------------------------------------------------------------------
/roles/media_center/tasks/desktop.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | # https://www.prahladyeri.com/blog/2016/02/minimal-debian-desktop-setup.html
4 | - name: Install packages for a minimal desktop
5 | apt: name={{ item }} state=latest
6 | with_items:
7 | - network-manager
8 | - xorg
9 | - openbox
10 | - xdm
11 | - xbacklight
12 | - pcmanfm
13 | - lxappearance
14 | - lxpanel
15 | - gmrun
16 | - gnome-terminal
17 |
18 | - name: "Create openbox config directory for {{ mc_user_name }}"
19 | file: path="{{ openbox_config_dir }}" owner="{{ mc_user_name }}" group="{{ mc_user_name }}" state=directory
20 | register: create_openbox_config
21 |
22 | - name: "Setting up default Openbox config for {{ mc_user_name }}"
23 | shell: cp /etc/xdg/openbox/* "{{ openbox_config_dir }}"
24 | become: yes
25 | become_user: "{{ mc_user_name }}"
26 | when: create_openbox_config|changed
27 |
28 | - name: Enforce lxpanel opening
29 | lineinfile: dest="{{ openbox_config_dir }}/autostart"
30 | regexp="^lxpanel"
31 | line="lxpanel &"
32 | create=yes
33 | state=present
34 | become: yes
35 | become_user: "{{ mc_user_name }}"
36 |
--------------------------------------------------------------------------------
/roles/media_center/tasks/kodi.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Add Kodi 16 Custom Repo
4 | apt_repository: repo={{ kodi_apt_repo }}
5 |
6 | - name: Add Kodi Repo Key
7 | apt_key:
8 | url: "http://pipplware.pplware.pt/pipplware/key.asc"
9 | state: present
10 |
11 | - name: Update APT package cache
12 | apt: update_cache=yes cache_valid_time=3600
13 |
14 | - name: Install Kodi
15 | apt: state=installed pkg=kodi force=yes
16 |
17 | - name: Add new user for media center
18 | user:
19 | name: "{{ kodi_username }}"
20 | comment: "Kodi session"
21 | password: "{{ kodi_password_hash }}"
22 | groups: cdrom,audio,video,plugdev,users,dialout,dip,input,netdev,{{ mc_shared_group }}
23 | append: yes
24 | state: present
25 |
26 | - name: "Create openbox config directory for {{ kodi_username }}"
27 | file: path="/home/{{ kodi_username }}/.config/openbox" owner="{{ kodi_username }}" group="{{ kodi_username }}" state=directory
28 | become: yes
29 | become_user: "{{ kodi_username }}"
30 |
31 | - name: Enforce Kodi opening in standalone mode
32 | lineinfile: dest="/home/{{ kodi_username }}/.config/openbox/autostart"
33 | regexp="^/usr/bin/kodi-standalone"
34 | line="/usr/bin/kodi-standalone &"
35 | create=yes
36 | state=present
37 | become: yes
38 | become_user: "{{ kodi_username }}"
39 |
40 | - name: Create current user configuration directories
41 | file: path={{ item }} state=directory owner={{ kodi_username }} group={{ kodi_username }} mode=0755
42 | with_items:
43 | - "{{ kodi_user_dir }}"
44 | - "{{ kodi_user_dir }}/userdata"
45 |
46 | - name: Build sources file
47 | template: src=kodi/sources.xml.j2 dest="{{ kodi_user_dir }}/userdata/sources.xml" owner={{ kodi_username }} group={{ kodi_username }} mode=0600
48 |
49 | - name: Enable buffering
50 | template: src=kodi/advancedsettings.xml.j2 dest="{{ kodi_user_dir }}/userdata/advancedsettings.xml" owner={{ kodi_username }} group={{ kodi_username }} mode=0600
51 | when: with_kodi_buffering
52 |
--------------------------------------------------------------------------------
/roles/media_center/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - include: base.yml
4 | - include: desktop.yml
5 | - include: kodi.yml
6 |
7 | - include: tvheadend.yml
8 | when: kodi_with_tvheadend
9 |
--------------------------------------------------------------------------------
/roles/media_center/tasks/tvheadend.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | # Install process based on https://tvheadend.org/projects/tvheadend/wiki/AptRepository
4 |
5 | - name: Install apt-transport-https
6 | apt: state=installed pkg=apt-transport-https
7 |
8 | - name: Add Tvheadend Repo
9 | apt_repository:
10 | repo: "{{ kodi_tvheadend_apt_repo }}"
11 | state: present
12 |
13 | - name: Add Tvheadend Repo Key
14 | apt_key:
15 | keyserver: keyserver.ubuntu.com
16 | id: 379CE192D401AB61
17 | state: present
18 |
19 | - name: Update APT package cache
20 | apt: update_cache=yes cache_valid_time=3600
21 |
22 | - name: Set Tvheadend User
23 | debconf:
24 | name: tvheadend
25 | question: tvheadend/admin_username
26 | vtype: string
27 | value: "{{ kodi_tvheadend_username }}"
28 |
29 | - name: Set Tvheadend Password
30 | debconf:
31 | name: tvheadend
32 | question: tvheadend/admin_password
33 | vtype: password
34 | value: "{{ kodi_tvheadend_password }}"
35 |
36 | - name: Install Tvheadend and Kodi PVR
37 | apt: state=installed pkg={{ item }}
38 | with_items:
39 | - tvheadend
40 | - kodi-pvr-hts
41 |
42 | - name: Enable Tvheadend service
43 | service:
44 | name: tvheadend
45 | enabled: yes
46 |
47 | - name: Start Tvheadend service
48 | service:
49 | name: tvheadend
50 | state: started
51 |
--------------------------------------------------------------------------------
/roles/media_center/templates/kodi/advancedsettings.xml.j2:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 1
6 | {{ kodi_buffer_size }}
7 | {{ kodi_read_buffer_factor }}
8 |
9 |
10 |
--------------------------------------------------------------------------------
/roles/media_center/templates/kodi/sources.xml.j2:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {% for source in kodi_source_files %}
7 |
8 | {{ source | basename }}
9 | {{ source }}
10 | true
11 |
12 | {% endfor %}
13 |
14 |
15 |
16 | {% for source in kodi_source_music %}
17 |
18 | {{ source | basename }}
19 | {{ source }}
20 | true
21 |
22 | {% endfor %}
23 |
24 |
25 |
26 | {% for source in kodi_source_pictures %}
27 |
28 | {{ source | basename }}
29 | {{ source }}
30 | true
31 |
32 | {% endfor %}
33 |
34 |
35 |
36 | {% for source in kodi_source_programs %}
37 |
38 | {{ source | basename }}
39 | {{ source }}
40 | true
41 |
42 | {% endfor %}
43 |
44 |
54 |
55 |
--------------------------------------------------------------------------------
/roles/media_center/templates/system/config.txt.j2:
--------------------------------------------------------------------------------
1 | # {{ ansible_managed }}
2 | #
3 | # For more options and information see
4 | # http://www.raspberrypi.org/documentation/configuration/config-txt.md
5 | # Some settings may impact device functionality. See link above for details
6 |
7 | # uncomment if you get no picture on HDMI for a default "safe" mode
8 | #hdmi_safe=1
9 |
10 | # uncomment this if your display has a black border of unused pixels visible
11 | # and your display can output without overscan
12 | #disable_overscan=1
13 |
14 | # uncomment the following to adjust overscan. Use positive numbers if console
15 | # goes off screen, and negative if there is too much border
16 | #overscan_left=16
17 | #overscan_right=16
18 | #overscan_top=16
19 | #overscan_bottom=16
20 |
21 | # uncomment to force a console size. By default it will be display's size minus
22 | # overscan.
23 | #framebuffer_width=1280
24 | #framebuffer_height=720
25 |
26 | # uncomment if hdmi display is not detected and composite is being output
27 | hdmi_force_hotplug=1
28 |
29 | # uncomment to force a specific HDMI mode (this will force VGA)
30 | #hdmi_group=1
31 | #hdmi_mode=1
32 |
33 | # uncomment to force a HDMI mode rather than DVI. This can make audio work in
34 | # DMT (computer monitor) modes
35 | #hdmi_drive=2
36 |
37 | # uncomment to increase signal to HDMI, if you have interference, blanking, or
38 | # no display
39 | config_hdmi_boost=4
40 |
41 | # uncomment for composite PAL
42 | #sdtv_mode=2
43 |
44 | #uncomment to overclock the arm. 700 MHz is the default.
45 | #arm_freq=800
46 |
47 | # Uncomment some or all of these to enable the optional hardware interfaces
48 | #dtparam=i2c_arm=on
49 | #dtparam=i2s=on
50 | #dtparam=spi=on
51 |
52 | # Uncomment this to enable the lirc-rpi module
53 | #dtoverlay=lirc-rpi
54 |
55 | # Additional overlays and parameters are documented /boot/overlays/README
56 |
57 | # Enable audio (loads snd_bcm2835)
58 | dtparam=audio=on
59 |
60 | ##################
61 | # Customizations #
62 | ##################
63 |
64 | # Allocating 256M of RAM as GPU Memory
65 | gpu_mem=192
66 |
67 | # Avoids bringing CEC (enabled TV) out of standby and channel switch when
68 | # rebooting.
69 | hdmi_ignore_cec_init=1
70 |
--------------------------------------------------------------------------------
/roles/media_center/templates/system/input-rules:
--------------------------------------------------------------------------------
1 | # {{ ansible_managed }}
2 |
3 | SUBSYSTEM==input, GROUP=input, MODE=0660
4 | KERNEL==tty[0-9]*, GROUP=tty, MODE=0660
5 |
--------------------------------------------------------------------------------
/roles/media_center/templates/system/permissions-rules:
--------------------------------------------------------------------------------
1 | # {{ ansible_managed }}
2 |
3 | # input
4 | KERNEL=="mouse*|mice|event*", MODE="0660", GROUP="input"
5 | KERNEL=="ts[0-9]*|uinput", MODE="0660", GROUP="input"
6 | KERNEL==js[0-9]*, MODE=0660, GROUP=input
7 | # tty
8 | KERNEL==tty[0-9]*, MODE=0666
9 | # vchiq
10 | SUBSYSTEM==vchiq, GROUP=video, MODE=0660
11 |
--------------------------------------------------------------------------------
/roles/rpi_docker/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | rd_storage_driver: overlay
4 | rd_tlscacert: /etc/docker/ca.pem
5 | rd_tlscert: /etc/docker/server.pem
6 | rd_tlskey: /etc/docker/server-key.pem
7 | rd_limit_nofile: 1048576
8 | rd_limit_nproc: 1048576
9 | rd_limit_core: infinity
10 | rd_iptables: false
11 | rd_always_restart: false
12 |
13 | rd_docker_bridge: docker0
14 | rd_docker_bridge_ip_range: 172.17.0.0/16
15 |
--------------------------------------------------------------------------------
/roles/rpi_docker/handlers/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Restart docker
4 | systemd: name=docker state=restarted daemon_reload=yes
5 |
6 | - name: Restart ufw
7 | systemd: name=ufw state=restarted
8 |
--------------------------------------------------------------------------------
/roles/rpi_docker/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Check for Hypriot installation
4 | set_fact:
5 | rpi_docker_hypriot: "{{ 'hypriotos' in ansible_kernel }}"
6 |
7 | - debug: msg="This is not an Hypriot image. This setup may fail, use it at your own risk"
8 | when: not rpi_docker_hypriot
9 |
10 | - debug: msg="Hypriot image detected"
11 | when: rpi_docker_hypriot
12 |
13 | - name: Install Docker custom service
14 | template: src=docker/docker.service
15 | dest=/etc/systemd/system/docker.service
16 | owner=root
17 | group=root
18 | mode=0644
19 | notify: Restart docker
20 |
21 | - name: Enable Docker daemon on boot
22 | service: name=docker enabled=true state=restarted
23 |
24 | - name: Install setuptools
25 | apt: name=python-setuptools state=latest
26 |
27 | # Using apt for pip was triggering an ImportError
28 | - name: Install pip
29 | easy_install: name=pip state=latest
30 |
31 | # See https://docs.ansible.com/ansible/guide_docker.html#requirements
32 | - name: Add Ansible required pip packages
33 | pip: name={{ item.name }} version={{ item.version }} state=present
34 | with_items:
35 | # Disabling docker-py install as the right version is set by docker-compose
36 | # Also, freezing docker-compose version to prevent this:
37 | # https://github.com/ansible/ansible/issues/20492
38 | #- { name: 'docker-py', version: '2.0' }
39 | - { name: 'docker-compose', version: '1.9.0' }
40 |
41 | - name: Update UFW default forward policy to ACCEPT
42 | lineinfile:
43 | dest=/etc/default/ufw
44 | regexp='^DEFAULT_FORWARD_POLICY='
45 | line='DEFAULT_FORWARD_POLICY="ACCEPT"'
46 | state=present
47 | when: not rd_iptables
48 | notify:
49 | - Restart ufw
50 |
51 | - name: Add Docker NAT rules for default docker bridge
52 | blockinfile:
53 | dest: /etc/ufw/before.rules
54 | insertbefore: '\*filter'
55 | block: |
56 | *nat
57 | :POSTROUTING ACCEPT [0:0]
58 | -A POSTROUTING ! -o {{ rd_docker_bridge }} -s {{ rd_docker_bridge_ip_range }} -j MASQUERADE
59 | COMMIT
60 | backup: yes
61 | state: present
62 | when: not rd_iptables
63 |
--------------------------------------------------------------------------------
/roles/rpi_docker/templates/docker/docker.service:
--------------------------------------------------------------------------------
1 | # {{ ansible_managed }}
2 | # HypriotOS only!
3 |
4 | [Service]
5 | Environment='DOCKER_OPTS=--iptables={{ rd_iptables }} --ip-masq={{ rd_iptables }}'
6 | ExecStart=/usr/bin/docker daemon -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock --storage-driver {{ rd_storage_driver }} --tlsverify --tlscacert {{ rd_tlscacert }} --tlscert {{ rd_tlscert }} --tlskey {{ rd_tlskey }} --label provider=generic "$DOCKER_OPTS"
7 | MountFlags=slave
8 | LimitNOFILE={{ rd_limit_nofile }}
9 | LimitNPROC={{ rd_limit_nproc }}
10 | LimitCORE={{ rd_limit_core }}
11 | {% if rd_always_restart %}
12 | Restart=always
13 | RestartSec=30
14 | {% endif %}
15 |
16 | [Install]
17 | WantedBy=multi-user.target
18 |
--------------------------------------------------------------------------------
/variables.yml.inc:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | ###############
4 | # Common Role #
5 | ###############
6 |
7 | # New Rpi identity
8 | server_user_name: neo
9 |
10 | # Must be a SHA-512 hash with like 5000 rounds (not as much as Ansible password_hash – 656000 — otherwise authentification may take a while)
11 | server_user_password_hash: $6$KU0KJ6OtZYzWlhbj$HWRhIMQL37UfEBkR1nwKre08LbM9LvAZr/lZ7iHz9VW7AfSu88RaADbtoxfJEwiW3DaWIzKUnu8mweeTg9Qxw.
12 |
13 | # To set custom user groups, use to following variable:
14 | # server_user_groups: "…"
15 |
16 | # Updating locales if required (default to en_US.UTF-8)
17 | server_locales:
18 | - fr_FR.UTF-8
19 |
20 | # Enable custom hostname for Rpi and install zeroconf and libnss-mdns
21 | # with_custom_hostname: True
22 | # server_host_name: "rpi-{{ server_user_name }}"
23 |
24 | # You can define a group shared between roles (here it will be rpi-neo-shared)
25 | # This is required for mixing server role with download_server and media_center
26 | # server_shared_group: mygroup
27 |
28 | # By default we allow server upgrade and reboot. You can disable this behaviour
29 | # with:
30 | # server_allow_upgrade: False
31 | # server_allow_reboot: False
32 |
33 | # Enable zsh with the line below:
34 | # with_zsh: True
35 | # server_zsh_theme: robbyrussell
36 |
37 | # SSMTP
38 |
39 | ssmtp_email: admin@gmail.com
40 |
41 | # https://security.google.com/settings/security/apppasswords
42 | ssmtp_auth_pass: MyUniqMailKey
43 |
44 | # Other vars:
45 | # ssmtp_mailhub: smtp.gmail.com:587
46 | # ssmtp_auth_user: admin@gmail.com
47 |
48 | # Logwatch
49 |
50 | logwatch_email: youremail@example.com
51 |
52 | # SSH
53 |
54 | # Changing this implies updating `allowed_ports` by adding the new port
55 | # AND leaving port 22 for the first setup (otherwise ansible may stop)
56 | ssh_port: 22
57 | ssh_public_keys:
58 | - ~/.ssh/id_rsa.pub
59 |
60 | # See roles/common/templates/ssh/banner.j2
61 | ssh_custom_banner: True
62 |
63 | # Firewall
64 |
65 | allowed_ports:
66 | - { port: 22, proto: 'tcp' }
67 | - { port: 6800, proto: 'tcp' } # Aria2 Daemon port
68 | # - { port: 8070, proto: 'tcp' } # Kodi HTTPServer Connection
69 | # - { port: 9777, proto: 'udp' } # Kodi EventServer Connection
70 | # - { port: 9981, proto: 'tcp' } # Tvheadend admin
71 |
72 | # Wifi handling (disabled by default)
73 |
74 | with_wifi: False
75 | wifi_ssid: my_ssid
76 | wifi_password: my_password
77 | wifi_proto: RSN
78 | wifi_key: WPA-PSK
79 | wifi_pairwise: CCMP
80 |
81 | with_vim: True
82 |
83 | # with_mosh: True
84 |
85 | # with_automount: True
86 | # By default the `automount_group` is `root` or `server_shared_group` if defined
87 |
88 | # Type and UUID are required. Mounted devices are available at /mnt/ for automount_group
89 | # To get those informations, run `sudo lsblk -f` against the host.
90 | # automount_local_devices:
91 | # - { name: 'mydevice', uuid: 'MY-DEVICE-UUID', type: 'ext4' }
92 |
93 | # Default type to cifs. Available at /media/ for automount_group
94 | # automount_network_folders:
95 | # - { name: 'mydrive', network_folder: '//192.168.0.10/mydrive', domain: 'WORKGROUP', user: 'my_name', password: 'my_password' }
96 |
97 | # It may also be a good idea to unprivileged the pi user after creating the new server owner
98 | # server_unsudoed_users:
99 | # - pi
100 |
101 | # You can enable sudo access without password for specific users.
102 | # This is not the default behavior.
103 | # server_sudo_nopasswd_users:
104 | # - neo
105 |
106 | # Fail2Ban
107 |
108 | server_fail2ban_services:
109 | - name: apache-auth-custom
110 | enabled: true
111 | port: http,https
112 | filter: apache-auth
113 | logpath: '/custom/apache/log/file.log'
114 | maxretry: 3
115 |
116 | server_fail2ban_email: admin+fail2ban@gmail.com
117 |
118 | ########################
119 | # Download server Role #
120 | ########################
121 |
122 | # Creates a dedicated user
123 |
124 | # Must be set in order to access aria2
125 | # aria2_rpc_token:
126 |
127 | # aria2_rpc_port: 6800
128 |
129 | # To enable traffic encryption (disabled by default)
130 | # aria2_rpc_encrypted: True
131 | # aria2_cert_subject: "/C=GB/ST=London/L=London/O=Global Security/OU=IT Department/CN=example.com"
132 |
133 | # ds_downloads_root (by default: /home/{{ ds_user_name }}). Will create a `downloads` directory to put downloaded files in.
134 | # ds_downloads_root: /media/mydrive
135 |
136 | #####################
137 | # Media Center Role #
138 | #####################
139 |
140 | # Does not create a dedicated user (eg. user neo)
141 |
142 | # kodi_source_files: []
143 | # kodi_source_music: []
144 | # kodi_source_pictures: []
145 | # kodi_source_programs: []
146 | # kodi_source_videos: []
147 |
148 | # Enable buffering for slow networks
149 | # with_kodi_buffering: True
150 |
151 | # Using 418M of RAM maximum (may crash if not available):
152 | # kodi_read_buffer_factor: 20
153 | # kodi_buffer_size: 139460608
154 |
155 | # Kodi session name and password are updatable (_default password is `kodi`_)
156 | # kodi_username: kodi
157 | # kodi_password_hash: $6$iLBM8EeXOMRMb0I3$gRrJmyVcCeMl.JT7DKLG1XIYDl6/qq6FB99t.IO2Hy87ykxKLSZ0GUVqK/.N7N5gfQMJHiBAAApCM.geeia1z.
158 |
159 | # Enable tvheadend installation with the following line (disabled by default)
160 | # kodi_with_tvheadend: true
161 | # You can also change these default values:
162 | # kodi_tvheadend_username: admin
163 | # kodi_tvheadend_password: admin
164 |
165 | ###################
166 | # Rpi Docker Role #
167 | ###################
168 |
169 | # Change the storage driver
170 | # rd_storage_driver: overlay
171 |
172 | # Update limits
173 | # rd_limit_nofile: 1048576
174 | # rd_limit_nproc: 1048576
175 | # rd_limit_core: infinity
176 |
177 | # Deactivate iptables addition by Docker (_thus enabling custom NAT and UFW forward policy_)
178 | # rd_iptables: false
179 |
180 | # Custom bridge (_if needed_)
181 | # rd_docker_bridge: docker0
182 | # rd_docker_bridge_ip_range: 172.17.0.0/16
183 |
184 | #####################
185 | # Audio Center Role #
186 | #####################
187 |
188 | # This role requires a PREMIUM Spotify account
189 | # The following variables are REQUIRED:
190 | #
191 | # ac_respot_url: https://url-to-a-valid-and-compatible-librespot-binary
192 | # ac_respot_checksum: 000000
193 | # ac_spotify_user: my-username
194 | # ac_spotify_password: my-password
195 |
196 | # These variables are optionnal:
197 | # ac_respot_output_dir: /usr/local/lib # directory where the binary is stored
198 | # ac_spotify_device_name: "{{ inventory_hostname }}" # device name used by Spotify
199 | # ac_respot_bitrate: 320 # Bitrate (320 is the maximum value)
200 | # ac_respot_device: hw:CARD=ALSA,DEV=0 # Audio device to play music with
201 | # ac_respot_backend: alsa # Audio backend
202 | # ac_respot_user_dir: '/etc/librespot' # librespot directory used to store cache
203 |
--------------------------------------------------------------------------------