From 83fee520d87188049c666a8258f5e81cda74c158 Mon Sep 17 00:00:00 2001 From: David Stephens Date: Mon, 28 Aug 2017 16:31:54 +0100 Subject: [PATCH] Initial Ansible NAS commit --- .gitignore | 2 + .gitmodules | 6 + LICENSE | 21 +++ README.md | 53 ++++++ ansible.cfg | 2 + group_vars/all.yml.dist | 152 ++++++++++++++++++ group_vars/vpn_credentials.yml.dist | 9 ++ inventory | 3 + nas.yml | 6 + roles/ansible-nas/tasks/couchpotato.yml | 26 +++ roles/ansible-nas/tasks/crashplan.yml | 26 +++ roles/ansible-nas/tasks/docker.yml | 34 ++++ roles/ansible-nas/tasks/duplicati.yml | 21 +++ roles/ansible-nas/tasks/general.yml | 28 ++++ roles/ansible-nas/tasks/glances.yml | 16 ++ roles/ansible-nas/tasks/main.yml | 19 +++ roles/ansible-nas/tasks/portainer.yml | 22 +++ roles/ansible-nas/tasks/sonarr.yml | 26 +++ roles/ansible-nas/tasks/transmission.yml | 30 ++++ .../tasks/transmission_with_openvpn.yml | 46 ++++++ roles/ansible-nas/templates/daemon.json | 4 + roles/docker | 1 + roles/samba | 1 + 23 files changed, 554 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 LICENSE create mode 100644 README.md create mode 100644 ansible.cfg create mode 100644 group_vars/all.yml.dist create mode 100644 group_vars/vpn_credentials.yml.dist create mode 100644 inventory create mode 100644 nas.yml create mode 100644 roles/ansible-nas/tasks/couchpotato.yml create mode 100644 roles/ansible-nas/tasks/crashplan.yml create mode 100644 roles/ansible-nas/tasks/docker.yml create mode 100644 roles/ansible-nas/tasks/duplicati.yml create mode 100644 roles/ansible-nas/tasks/general.yml create mode 100644 roles/ansible-nas/tasks/glances.yml create mode 100644 roles/ansible-nas/tasks/main.yml create mode 100644 roles/ansible-nas/tasks/portainer.yml create mode 100644 roles/ansible-nas/tasks/sonarr.yml create mode 100644 roles/ansible-nas/tasks/transmission.yml create mode 100644 roles/ansible-nas/tasks/transmission_with_openvpn.yml create mode 100644 roles/ansible-nas/templates/daemon.json create mode 160000 roles/docker create mode 160000 roles/samba diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a4a9481d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +group_vars/all.yml +group_vars/vpn_credentials.yml diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..cf61e31a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "roles/samba"] + path = roles/samba + url = https://github.com/bertvv/ansible-role-samba.git +[submodule "roles/docker"] + path = roles/docker + url = https://github.com/geerlingguy/ansible-role-docker.git diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..aefd895c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 David Stephens + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..e7d3efac --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +### Ansible NAS + +After getting burned by broken FreeNAS updates one too many times, I figured I could do a much better job myself using +just a stock Ubuntu install, some clever Ansible config and a bunch of docker containers. + +## What This Provides + + * Any number of Samba shares for you to store your stuff + * Via Docker: + - [Duplicati](https://www.duplicati.com/) for backing up your stuff + - [Transmission](https://transmissionbt.com/) BitTorrent client (with OpenVPN if you have a supported VPN provider) + - [Sonarr](https://sonarr.tv/) for downloading and managing TV episodes + - [CouchPotato](https://couchpota.to/) for downloading and managing movies + - [Portainer](https://portainer.io/) for managing Docker and running custom images + - [Glances](https://nicolargo.github.io/glances/) for seeing the state of your system via a web browser + +## What This Doesn't Do + +Ansible NAS doesn't set up your disk partitions, primarily because getting it wrong can be incredibly destructive. +That aside, configuring partitions is usually a one-time (or very infrequent) event, so there's not much to be +gained by automating it. + +## Hardware + +Ansible NAS should work on any recent Ubuntu box. Development was done on Ubuntu 16.04.3 LTS. + +## How To Use + +1. `git clone https://www.github.com/davestephens/ansible-nas && cd ansible-nas` +2. Copy `group_vars/all.yml.dist` to `group_vars/all.yml`. +3. Open up `group_vars/all.yml` and follow the instructions there for configuring your Ansible NAS. +3. If you plan to use Transmission with OpenVPN, also copy `group_vars/vpn_credentials.yml.dist` to +`group_vars/vpn_credentials.yml` and input your settings. +4. Modify `inventory` and update it with the hostname of your NAS box. +5. Run the playbook - something like `ansible-playbook -i inventory nas.yml` should do you nicely. + +## Migrating from FreeNAS + +Assuming that your Ubuntu system disk is separate from your storage (it should be!): + +1. Disconnect your drives. +2. Run Ansible NAS against your server. +3. Reconnect your drives. +4. SSH to the server and run `zpool import` to determine available ZFS pools. +5. `zpool import ` against the pools you want to attach. +6. `chown -R root:root /mnt/` to fix the ownership of the data + +## TODO +1. Handle Docker containers being enabled then subsequently disabled (i.e clean up afterwards) +2. SMART disk monitoring + +## Contributing +Contributions welcome! diff --git a/ansible.cfg b/ansible.cfg new file mode 100644 index 00000000..48bc18c4 --- /dev/null +++ b/ansible.cfg @@ -0,0 +1,2 @@ +[defaults] +retry_files_enabled = False diff --git a/group_vars/all.yml.dist b/group_vars/all.yml.dist new file mode 100644 index 00000000..efb07b8e --- /dev/null +++ b/group_vars/all.yml.dist @@ -0,0 +1,152 @@ +### +### Ansible NAS Features +### +# Set these options to true or false to toggle specific features + +# BitTorrent +# If you plan to use Transmission with OpenVPN, you'll need to copy group_vars/vpn_credentials.yml.dist +# to group_vars/vpn_credentials.yml, then update it with your own settings. +transmission_with_openvpn_enabled: true +transmission_enabled: false + +# Media Sourcing +sonarr_enabled: true +couchpotato_enabled: false + +# System Management +portainer_enabled: true +glances_enabled: true + +# Backup & Restore +crashplan_enabled: true +duplicati_enabled: true + + +### +### General +### +# Sets the hostname of your Ansible NAS +ansible_nas_hostname: bender + +# Update all apt packages when playbook is run +keep_packages_updated: false + +# Will be added to the docker group to give user command line access to docker +ansible_nas_user: david + +# Where you want Docker to store its images and container data. +docker_home: /mnt/Volume2/docker + +# Your time zone, passed to Docker containers at startup +time_zone: Europe/London + + +### +### Samba +### +# The location where all shares will be created by default. Can be overridden on a per-share basis. +# This path will be mounted to backup containers, ie Crashplan, Duplicati +samba_shares_root: /mnt/Volume3 + +# The account used when Samba shares are accessed. Shouldn't need to change this unless you want to +# mess with Samba user permissions. +samba_guest_account: nobody + +# Shares you want published over Samba. +samba_shares: + - name: downloads + comment: 'Stuff downloaded' + guest_ok: yes + public: yes + writable: yes + path: "{{ samba_shares_root }}/downloads" + + - name: movies + comment: 'Movies' + guest_ok: yes + public: yes + writable: yes + path: "{{ samba_shares_root }}/movies" + + - name: tv + comment: 'TV Episodes' + guest_ok: yes + public: yes + writable: yes + path: "{{ samba_shares_root }}/tv" + + - name: dump + comment: 'File dump' + guest_ok: yes + public: yes + writable: yes + path: "{{ samba_shares_root }}/dump" + + - name: games + comment: 'Games' + guest_ok: yes + public: yes + writable: yes + path: "{{ samba_shares_root }}/games" + + - name: photos + comment: 'Pictures' + guest_ok: yes + public: yes + writable: yes + path: "{{ samba_shares_root }}/photos" + +################################################################## +###### You shouldn't need to edit anything below this point ###### +################################################################## + +### +### Transmission +### +transmission_config_directory: "{{ docker_home }}/transmission/config" +transmission_download_directory: "{{ samba_shares_root }}/downloads" +transmission_watch_directory: "{{ samba_shares_root }}/torrents" +transmission_user_id: 0 +transmission_group_id: 0 +transmission_local_network: "192.168.1.0/24" + + +### +### Duplicati +### +duplicati_data_directory: "{{ docker_home }}/duplicati/config" + + +### +### Sonarr +### +sonarr_data_directory: "{{ docker_home }}/sonarr/config" +sonarr_tv_directory: "{{ samba_shares_root }}/TV" +sonarr_user_id: 0 +sonarr_group_id: 0 + + +### +### OpenVPN +### +openvpn_config_directory: "{{ docker_home }}/openvpn" + + +### +### Portainer +### +portainer_data_directory: "{{ docker_home }}/portainer/config" + + +### +### Crashplan +### +crashplan_user_id: 0 +crashplan_group_id: 0 + + +### +### Couchpotato +### +couchpotato_user_id: 0 +couchpotato_group_id: 0 \ No newline at end of file diff --git a/group_vars/vpn_credentials.yml.dist b/group_vars/vpn_credentials.yml.dist new file mode 100644 index 00000000..fa6872d3 --- /dev/null +++ b/group_vars/vpn_credentials.yml.dist @@ -0,0 +1,9 @@ +### +### VPN Credentials +### +# If you're using Transmission with a VPN, you'll need to set these credentials. +# See https://hub.docker.com/r/haugene/transmission-openvpn/ for supported VPN providers. +openvpn_username: super_secret_username +openvpn_password: super_secret_password +openvpn_provider: NORDVPN +openvpn_config: uk64.nordvpn.com.udp1194 \ No newline at end of file diff --git a/inventory b/inventory new file mode 100644 index 00000000..19e31736 --- /dev/null +++ b/inventory @@ -0,0 +1,3 @@ +[all] +192.168.1.30 + diff --git a/nas.yml b/nas.yml new file mode 100644 index 00000000..d3774435 --- /dev/null +++ b/nas.yml @@ -0,0 +1,6 @@ +--- +- hosts: all + roles: + - samba + - docker + - ansible-nas diff --git a/roles/ansible-nas/tasks/couchpotato.yml b/roles/ansible-nas/tasks/couchpotato.yml new file mode 100644 index 00000000..f19602ae --- /dev/null +++ b/roles/ansible-nas/tasks/couchpotato.yml @@ -0,0 +1,26 @@ +--- +- name: Create Couchpotato Directories + file: + path: "{{ item }}" + state: directory + # mode: 0755 + with_items: + - "{{ docker_home }}/couchpotato/config" + +- name: Couchpotato Docker Container + docker_container: + name: couchpotato + image: linuxserver/couchpotato + pull: true + volumes: + - "{{ docker_home }}/couchpotato/config:/config:rw" + - "{{ samba_shares_root }}/BitTorrent/completed:/downloads:rw" + - "{{ samba_shares_root }}/Movies/Movies:/movies:rw" + - "/etc/timezone:/etc/timezone:ro" + ports: + - "5050:5050" + env: + PUID: "{{ couchpotato_user_id }}" + PGID: "{{ couchpotato_group_id }}" + restart_policy: unless-stopped + memory: 1g \ No newline at end of file diff --git a/roles/ansible-nas/tasks/crashplan.yml b/roles/ansible-nas/tasks/crashplan.yml new file mode 100644 index 00000000..a13821e0 --- /dev/null +++ b/roles/ansible-nas/tasks/crashplan.yml @@ -0,0 +1,26 @@ +--- +- name: Create Crashplan Directories + file: + path: "{{ item }}" + state: directory + # mode: 0755 + with_items: + - "{{ docker_home }}/crashplan/config" + +- name: Crashplan Docker Container + docker_container: + name: crashplan + image: jlesage/crashplan + pull: true + volumes: + - "{{ docker_home }}/crashplan/config:/config:rw" + - "{{ samba_shares_root }}:/storage:ro" + - "/etc/timezone:/etc/timezone:ro" + ports: + - "5800:5800" + - "5900:5900" + env: + USER_ID: "{{ crashplan_user_id }}" + GROUP_ID: "{{ crashplan_group_id }}" + restart_policy: unless-stopped + memory: 2g \ No newline at end of file diff --git a/roles/ansible-nas/tasks/docker.yml b/roles/ansible-nas/tasks/docker.yml new file mode 100644 index 00000000..dcd4217c --- /dev/null +++ b/roles/ansible-nas/tasks/docker.yml @@ -0,0 +1,34 @@ +--- +- name: install pip + apt: + name: python-pip + state: present + +- name: 'Install docker-py' + pip: + name: docker-py + state: present + +- name: create docker home + file: + path: "{{ docker_home }}" + mode: 0755 + state: directory + +- name: add user account to docker group + user: + name: "{{ ansible_nas_user }}" + group: docker + append: yes + +- name: update docker home from install default + template: + src: daemon.json + dest: /etc/docker/daemon.json + register: docker_config + +- name: restart docker + service: + name: docker + state: restarted + when: docker_config.changed \ No newline at end of file diff --git a/roles/ansible-nas/tasks/duplicati.yml b/roles/ansible-nas/tasks/duplicati.yml new file mode 100644 index 00000000..67cdd2b5 --- /dev/null +++ b/roles/ansible-nas/tasks/duplicati.yml @@ -0,0 +1,21 @@ +--- +- name: Create Duplicati Directory + file: + path: "{{ item }}" + state: directory + with_items: + - "{{ duplicati_data_directory }}" + +- name: Dupicati Docker Container + docker_container: + name: duplicati + image: linuxserver/duplicati + pull: true + ports: + - "8200:8200" + volumes: + - "{{ duplicati_data_directory }}:/config:rw" + - "{{ samba_shares_root }}:/source:ro" + - "/etc/timezone:/etc/timezone:ro" + restart_policy: unless-stopped + memory: 4g diff --git a/roles/ansible-nas/tasks/general.yml b/roles/ansible-nas/tasks/general.yml new file mode 100644 index 00000000..2969d364 --- /dev/null +++ b/roles/ansible-nas/tasks/general.yml @@ -0,0 +1,28 @@ +--- +- name: Update apt-cache + apt: + update_cache: yes + cache_valid_time: 3600 + +- name: Upgrade all packages + apt: + name: "*" + state: latest + when: keep_packages_updated == true + +- name: Install some packages + apt: + name: "{{ item }}" + state: present + with_items: + - smartmontools + - htop + - zfs + - bonnie++ + - unzip + +# - name: Configure smartmontools + +- name: "Set hostname to {{ ansible_nas_hostname }}" + hostname: + name: "{{ ansible_nas_hostname }}" \ No newline at end of file diff --git a/roles/ansible-nas/tasks/glances.yml b/roles/ansible-nas/tasks/glances.yml new file mode 100644 index 00000000..60721881 --- /dev/null +++ b/roles/ansible-nas/tasks/glances.yml @@ -0,0 +1,16 @@ +- name: Glances Docker Container + docker_container: + name: glances + image: nicolargo/glances + pull: true + ports: + - "61208:61208" + - "61209:61209" + volumes: + #- "/glances.conf:/glances/conf/glances.conf" + - "/var/run/docker.sock:/var/run/docker.sock:ro" + - "/etc/timezone:/etc/timezone:ro" + env: + GLANCES_OPT: "-w" + restart_policy: unless-stopped + memory: 1g diff --git a/roles/ansible-nas/tasks/main.yml b/roles/ansible-nas/tasks/main.yml new file mode 100644 index 00000000..7ac6efcb --- /dev/null +++ b/roles/ansible-nas/tasks/main.yml @@ -0,0 +1,19 @@ +--- +- include: general.yml +- include: docker.yml +- include: portainer.yml + when: portainer_enabled == true +- include: transmission.yml + when: transmission_enabled == true +- include: transmission_with_openvpn.yml + when: transmission_with_openvpn_enabled == true +- include: sonarr.yml + when: sonarr_enabled == true +- include: glances.yml + when: glances_enabled == true +- include: duplicati.yml + when: duplicati_enabled == true +- include: crashplan.yml + when: crashplan_enabled == true +- include: couchpotato.yml + when: couchpotato_enabled == true \ No newline at end of file diff --git a/roles/ansible-nas/tasks/portainer.yml b/roles/ansible-nas/tasks/portainer.yml new file mode 100644 index 00000000..bb072c9d --- /dev/null +++ b/roles/ansible-nas/tasks/portainer.yml @@ -0,0 +1,22 @@ +--- +- name: Create Portainer Directories + file: + path: "{{ item }}" + state: directory + with_items: + - "{{ portainer_data_directory }}" + +- name: Portainer Docker Container + docker_container: + name: portainer + image: portainer/portainer + pull: true + volumes: + - "{{ portainer_data_directory }}:/data:rw" + - "/var/run/docker.sock:/var/run/docker.sock:ro" + - "/etc/timezone:/etc/timezone:ro" + ports: + - "9000:9000" + restart_policy: unless-stopped + memory: 1g + diff --git a/roles/ansible-nas/tasks/sonarr.yml b/roles/ansible-nas/tasks/sonarr.yml new file mode 100644 index 00000000..b3fd2f33 --- /dev/null +++ b/roles/ansible-nas/tasks/sonarr.yml @@ -0,0 +1,26 @@ +--- +- name: Create Sonarr Directories + file: + path: "{{ item }}" + state: directory + with_items: + - "{{ sonarr_data_directory }}" + +- name: Sonarr + docker_container: + name: sonarr + image: linuxserver/sonarr + pull: true + volumes: + - "/etc/localtime:/etc/localtime:ro" + - "{{ sonarr_tv_directory }}:/tv:rw" + - "{{ transmission_download_directory }}/complete:/downloads:rw" + - "{{ sonarr_data_directory }}:/config:rw" + - "/etc/timezone:/etc/timezone:ro" + ports: + - "8989:8989" + env: + PUID: "{{ sonarr_user_id }}" + PGID: "{{ sonarr_group_id }}" + restart_policy: unless-stopped + memory: 1g diff --git a/roles/ansible-nas/tasks/transmission.yml b/roles/ansible-nas/tasks/transmission.yml new file mode 100644 index 00000000..457768f7 --- /dev/null +++ b/roles/ansible-nas/tasks/transmission.yml @@ -0,0 +1,30 @@ +--- +- name: Create Transmission Directories + file: + path: "{{ item }}" + state: directory + # mode: 0755 + with_items: + - "{{ transmission_config_directory }}" + - "{{ transmission_download_directory }}" + - "{{ transmission_watch_directory }}" + +- name: Transmission Docker Container + docker_container: + name: transmission + image: linuxserver/transmission + pull: true + volumes: + - "{{ transmission_config_directory }}:/config:rw" + - "{{ transmission_download_directory }}:/downloads:rw" + - "{{ transmission_watch_directory }}:/watch:rw" + - "/etc/timezone:/etc/timezone:ro" + ports: + - "9092:9091" + - "51414:51413" + env: + PUID: "{{ transmission_user_id }}" + PGID: "{{ transmission_group_id }}" + restart_policy: unless-stopped + memory: 1g + diff --git a/roles/ansible-nas/tasks/transmission_with_openvpn.yml b/roles/ansible-nas/tasks/transmission_with_openvpn.yml new file mode 100644 index 00000000..75af9604 --- /dev/null +++ b/roles/ansible-nas/tasks/transmission_with_openvpn.yml @@ -0,0 +1,46 @@ +--- +- include_vars: group_vars/vpn_credentials.yml + +- name: Create Transmission Directories + file: + path: "{{ item }}" + state: directory + # mode: 0755 + with_items: + - "{{ transmission_config_directory }}" + - "{{ transmission_download_directory }}" + - "{{ transmission_watch_directory }}" + +- name: Transmission with VPN + docker_container: + name: transmission-openvpn + image: haugene/transmission-openvpn + pull: true + volumes: + - "/etc/localtime:/etc/localtime:ro" + - "{{ transmission_download_directory }}:/storage/downloads:rw" + - "{{ transmission_config_directory }}:/config:rw" + - "{{ transmission_watch_directory }}:/storage/watch:rw" + - "/etc/timezone:/etc/timezone:ro" + ports: + - "9091:9091" + - "51413:51413" + env: + TRANSMISSION_HOME: /config + TRANSMISSION_DOWNLOAD_DIR: /storage/downloads/complete + TRANSMISSION_INCOMPLETE_DIR: /storage/downloads/incomplete + TRANSMISSION_WATCH_DIR: /storage/watch + OPENVPN_PROVIDER: "{{ openvpn_provider }}" + OPENVPN_USERNAME: "{{ openvpn_username }}" + OPENVPN_PASSWORD: "{{ openvpn_password }}" + OPENVPN_CONFIG: "{{ openvpn_config }}" + PUID: "{{ transmission_user_id }}" + PGID: "{{ transmission_group_id }}" + LOCAL_NETWORK: "{{ transmission_local_network }}" + ENABLE_UFW: false + devices: + - /dev/net/tun + capabilities: + - NET_ADMIN + restart_policy: unless-stopped + memory: 1g diff --git a/roles/ansible-nas/templates/daemon.json b/roles/ansible-nas/templates/daemon.json new file mode 100644 index 00000000..3c8f9868 --- /dev/null +++ b/roles/ansible-nas/templates/daemon.json @@ -0,0 +1,4 @@ +{ + "graph": "{{ docker_home }}/data", + "storage-driver": "overlay" +} \ No newline at end of file diff --git a/roles/docker b/roles/docker new file mode 160000 index 00000000..0960f85f --- /dev/null +++ b/roles/docker @@ -0,0 +1 @@ +Subproject commit 0960f85f784fbaaee9d2e3a606db7899542ae2fb diff --git a/roles/samba b/roles/samba new file mode 160000 index 00000000..b63ca138 --- /dev/null +++ b/roles/samba @@ -0,0 +1 @@ +Subproject commit b63ca1385ee8e186b6b4060778b780512c6a37d7