This article is part of a multi-post series about making the most out of an entry-level Synology NAS:

My Synology NAS

I have an entry-level Synology NAS with an ARM v7 CPU.

This little box is a nice plug-n-play solution for basic NAS usage.

Everything is configurable via an easy to use web GUI and additional packages (or should I say “Apps”) can be installed via a dedicated section.

But the selection of those packages is pretty limited.

Community packages can be added but they are not well maintained (old software versions, installation script buggy or with unclear dependencies…).

Sadly, even though Docker is supported on this CPU architecture (thank you Raspberry Pi) the installed kernel version (3.2.40) is too old to support it.

So, if we want to make the most of this little device, we have to lift up the hood, realize that the OS (called DSM) is nothing more than a tweaked Linux.

Ansible

I really like Ansible.

After years of using custom bash scripts (plus some forgettable bewilderments with Chef) it really changed how I approach provisioning when I discovered it 3 years ago.

To me it’s main strengths are that:

Don’t read me wrong: it’s not a panacea. Being very declarative and procedural it’s not suited for complex orchestration.

So it’s natural that I want to use Ansible to install stuff on my little NAS.

Enabling SSH access

Ansible uses SSH to communicate with remote hosts.

SSH access is disabled by default on DSM.

So first of all, we need to enable it.

The official documentation is pretty straightforward for this.

Getting Ansible to communicate with the NAS

To give access to a server from my Ansible host, I generally create an ansible user of the target server with passwordless sudo access.

And what better way to do this than through an Ansible playbook?

The only trick here is to log in using an “administrator” account using its password.

One small annoyance is that the Linux OS on the NAS doesn’t come with commands such as useradd or groups. As a result the user and group modules couldn’t be used.

Instead, we need to rely on alternative commands synouser and synogroup.

- hosts: <my-nas>
  remote_user: <my-nas-user>
  gather_facts: False

  vars:
    my_remote_user: <my-nas-user>
    my_remote_password: '<my-nas-user-pwd>'
    my_ansible_username: ansible
    my_ansible_password: '<my-secure-ansible-password>'
    my_ansible_public_key_path: /root/.ssh/ansible.pub

  tasks:

  # log in
  - name: try login in as user with my password
    command: sshpass -p "{{ my_remote_password }}" ssh -q -l {{ my_remote_user }} "{{ ansible_host }}" -o PreferredAuthentications=password -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=1 echo "Worked"
    register: ansible_check_connect_user_pwd
    connection: local
    ignore_errors: yes
    changed_when: False
  - name: if password worked, use it
    connection: local
    set_fact:
      ansible_ssh_pass: "{{ my_remote_password }}"
      ansible_sudo_pass: "{{ my_remote_password }}"
    when: ansible_check_connect_user_pwd is succeeded

  - name: gather facts
    become: true
    setup:

  # retrieve current list of administrators
  - name: get list of administrators from /etc/group file
    become: true
    shell: cat /etc/group | grep administrators | sed 's/.*://'
    register: administrators
  - name: split list of administrators
    set_fact:
      administrators_list: "{{ administrators.stdout.split(',') }}"

  # create user
  - name: read /etc/passwd file
    become: true
    shell: cat /etc/passwd
    register: etc_passwd
  - name: create user ansible
    when: etc_passwd.stdout.find(my_ansible_username) == -1
    become: true
    # NB: args are [username pwd "full name" expired{0|1} mail AppPrivilege]
    # only using AppPrivilege 0x01, i.e. FTP
    command: /usr/syno/sbin/synouser --add {{ my_ansible_username }} "{{ my_ansible_password }}" "" 0 "" 1
    args:
      creates: /volume1/homes/homes/{{ my_ansible_username }}
  - name: fix user ansible home permission
    become: true
    file:
      path: /volume1/homes/{{ my_ansible_username }}
      mode: u=rwx,g=rx,o=rx

  # add user to administrators
  - name: add user ansible to administrators group
    become: true
    command: /usr/syno/sbin/synogroup --member administrators {{ ' '.join(administrators_list) }} {{ my_ansible_username }}

  # give user passwordless sudo access
  - name: give user ansible sudo access
    become: true
    lineinfile:
      dest: /etc/sudoers.d/ansible
      line: "{{ my_ansible_username }} ALL=(ALL) NOPASSWD: ALL"
      regexp: "^{{ my_ansible_username }}"
      state: present
      create: yes
      mode: 0440
      # NB: no visudo on this OS...
      # validate: '/usr/sbin/visudo -cf %s'

  # deploy cert
  - name: deploy ansible master certificate
    become: true
    authorized_key:
      state: present
      user: "{{ my_ansible_username }}"
      key: "{{ lookup('file', my_ansible_public_key_path) }}"
      manage_dir: yes

Then reboot your NAS for some changes to take effect (adding the ansible user to the administrators group).

You should then be able to ansible-ping it:

$ ansible nas -m ping -u ansible
nas | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}

Now we’re free to use Ansible like we would with any other device.

Complementary information

Synology provides some documentation regarding DSM:

It seems that Github user Andrew Gaffney already did some legwork to make integrate the File Station HTTP API with Ansible: agaffney/ansible-synology-dsm.


Tagged #ansible, #synology.