├── .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 | --------------------------------------------------------------------------------