ODROID-HC2 and Ansible

After installing Fedora 29 on my ODROID-HC2 nodes, I want to get an ansible user onto them so that my usual Ansible playbooks can configure them.

Overview of steps

  1. Ensure that both python and libselinux-python are installed on the ODROID
  2. Ensure your ODROID-HC2 is in your inventory
  3. Set up variables as needed
  4. Ensure you can ansible … -m ping …
  5. optional: Create an ansible user on the ODROID-HC2 by doing one of
    • using a playbook that connects as user root
    • using shell commands
  6. optional: Run all future playbooks as the ansible user

If you’re interested, there’s also details on my own Ansible setup further below. It includes:

Prerequisites

  1. An OS supported by Ansible on your ODROID-HC2, I installed Fedora on mine
  2. Ability to log into the ODROID-HC2 via ssh as root using a ssh key

Should you be unable to ssh, then you will probablyt need a serial console to fix it. See installing Fedora 29 on my ODROID-HC2 for details on the serial console.

Prepare node for Ansible

Since the image Fedora-Minimal-armhfp-29-1.2 is lacking 2 packages needed for ansible to control the node, install them manually.

On the ODROID, as root

dnf install python libselinux-python

NOTE: be patient with your first use of dnf, it can take a few minutes. This improves after dnf-makecache ran once. You can check the status of dnf-makecache.service and system activity on the ODROID, as root, by using

journalctl _SYSTEMD_UNIT=dnf-makecache.service
top -bn1 | head -n 10

Verify Ansible Connectivity

Verify that you can reach the ODROID with Ansible. For now as user root, as no ansible user in Fedora-Minimal-armhfp-29-1.2-sda.raw

On the Ansible control node

ansible odroid-hc2-00 -m ping -e ansible_user=root

Using -e ansible_user=root ensures it takes highest variable precedence. (thanks to jtanner for the doc pointer). Unless of course you created a user for Ansible during your OS installation of the ODROID-HC2. Then you drop the option, ansible odroid-hc2-00 -m ping

It should return this

odroid-hc2-00 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

If this fails, you will need to ssh -v (with your key) to the ODROID as root and debug why Ansible can not connect.

Configure your ODROID with Ansible

At this point, you can configure your ODROID-HC2 nodes’ Fedora with Ansible. Hopefully you wrote your playbooks and roles without hardcoding x86_64 ;-)

Notes:

  • use the ssh privkey matching the pubkey you installed on the ODROID
    • I recomend using ssh-agent if you Ansible from a shell or development environment
    • I recommend a credentials store if you use Ansible Tower.
  • as my installation instructions create only a root user, initially start with an Ansible play that connects as root and sets up an ansible user.
    • do not forget to set up password-less sudo.

You may want to continue reading to get an idea of the steps I perform.

my Ansible setup

Note that the below is just what I used while creating this post.

You probably want to use your own playbooks and roles now that you learned what needs doing.

This section mainly serves as a note to self.

group_vars

inventories/group_vars/ceph-housenet.yml contains the following

---
user_owner: pcfe
ansible_user: ansible
common_timezone: Europe/Berlin

sample host_vars

inventories/host_vars/odroid-hc2-00.yml

ansible_python_interpreter: /usr/bin/python3
network_connections:
  - name: "Wired connection 1"
    type: "ethernet"
    interface_name: "eth0"
    zone: "public"
    state: up
    ip:
      dhcp4:      no
      auto6:      no
      gateway4:   192.168.50.251
      dns:        192.168.50.252
      dns_search: internal.pcfe.net
      address:    192.168.50.160/24

ansible_python_interpreter because I use the firewalld module in my module pcfe.check_mk.

inventory

inventories/ceph-HouseNet-cluster.ini contains the following

#[softiron]
#overdrive-1000

[odroids]
odroid-hc2-[00:06]

[ceph-x86-nodes]
ceph-ansible
ceph-dashboard
ceph-rgw
#altespielbuechse

[ceph-arm-nodes:children]
odroids
#softiron

[ceph-fedora-nodes:children]
odroids

[ceph-el-nodes:children]
ceph-x86-nodes
#softiron

[ceph-housenet:children]
ceph-arm-nodes
ceph-x86-nodes

[ceph-housenet:vars]
ansible_user=ansible

[mons]
odroid-hc2-[00:02]

# MGRs are typically collocated with MONs
[mgrs]
odroid-hc2-[00:02]

[osds]
odroid-hc2-[00:06]
#overdrive-1000

# ceph-grafana should not live on an OSD node
[ceph-grafana]
ceph-dashboard

# RADOS Gateway running as a VM is supported
# https://access.redhat.com/articles/1548993
[rgws]
ceph-rgw

[mdss]
odroid-hc2-03

#[clients]
#karhu
#openshift-master
#openshift-node-01
#openshift-router

My Initial Setup Playbook

My Playbook arm-fedora-initial-setup.yml uses the following private roles

  • pcfe.user_owner is abused to create the ansible user
  • pcfe.basic-security-setup sets non-negotiable security settings
  • pcfe.housenet sets up timezone and the use of local package mirrors

It will also

  • set up chrony
  • apply all errata
  • ensure the ansible user can sudo without a password

The play arm-fedora-initial-setup.yml is as follows

# initially sets up my ARM based boxes
# you can run this after completing the steps at
# https://blog.pcfe.net/hugo/posts/2019-01-27-fedora-29-on-odroid-hc2/
#
# this also works for boxes installed with 
# Fedora-Server-dvd-aarch64-29-1.2.iso
#
# this initial setup Playbook must connect as user root,
# after it ran we can connect as user ansible.
# since user_owner is set (in vars: below) to 'ansible',
# pcfe.user_owner creates the user 'ansible' and drops in ssh pubkeys
#
# this is for my ODROID-HC2 boxes and my OverDrive 1000
#
- hosts:
  - odroids
  - softiron
  become: no
  roles:
    - pcfe.user_owner
    - pcfe.basic-security-setup
    - pcfe.housenet

  vars:
    ansible_user: root
    user_owner: ansible

  tasks:
    # start by enabling time sync, while my ODROIDs do have the RTC battery add-on, yours might not.
    # Plus it's nice to be able to wake up the boards from poweroff
    # and have the correct time alredy before chrony-wait runs at boot
    - name:         "CHRONYD | ensure chrony-wait is enabled"
      service:
        name:       chrony-wait
        enabled:    true
    - name:         "CHRONYD | ensure chronyd is enabled and running"
      service:
        name:       chronyd
        enabled:    true
        state:      started

    # enable persistent journal
    - name: "JOURNAL | ensure persistent logging for the systemd journal is possible"
      file:
        path: /var/log/journal
        state: directory
        owner: root
        group: systemd-journal
        mode: 0755

    # enable passwordless sudo for the created ansible user
    - name: "SUDO | enable passwordless sudo for ansible user"
      copy:
        dest: /etc/sudoers.d/ansible
        content: |
          ansible   ALL=NOPASSWD:   ALL
        owner: root
        group: root
        mode: 0440

    # I do want all errata applied
    - name: "DNF | ensure all updates are applied"
      dnf:
        update_cache: yes
        name: '*'
        state: latest
      tags: apply_errata

Output of Initial Setup Playbook

So that you can get an idea of what the roles used do, the full log follows. Still, you most probably want to use your own playbooks and roles.

ansible-playbook -i ../inventories/ceph-HouseNet-cluster.ini -l odroid-hc2-00 --skip-tags apply_errata arm-fedora-initial-setup.yml | tee ../../hugo-blog.pcfe.net/static/code/arm-fedora-initial-setup.log

(In this example, I skip applying errata in order to get this log output quicker)


PLAY [ceph-arm-nodes] ***********************************************************************************************

TASK [Gathering Facts] **********************************************************************************************
ok: [odroid-hc2-00]

TASK [pcfe.user_owner : USER OWNER | ensure group ansible exists] ***************************************************
ok: [odroid-hc2-00]

TASK [pcfe.user_owner : USER OWNER | ensure user ansible exists] ****************************************************
ok: [odroid-hc2-00]

TASK [pcfe.user_owner : USER OWNER | ensure authorized key for ansible exists] **************************************
ok: [odroid-hc2-00]

TASK [pcfe.user_owner : USER OWNER | ensure authorized key for root exists] *****************************************
ok: [odroid-hc2-00]

TASK [pcfe.basic-security-setup : BASIC SEC | ensure selinux is running with enforcing] *****************************
ok: [odroid-hc2-00]

TASK [pcfe.basic-security-setup : BASIC SEC | ensure ssh auth is via ssh-key only] **********************************
ok: [odroid-hc2-00]

TASK [pcfe.housenet : TIMEZONE | ensure timezone is Europe/Berlin] **************************************************
ok: [odroid-hc2-00]

TASK [pcfe.housenet : YUM | ensure repo CentOS-Updates-HouseNet is enabled if on CentOS >= 7.0] *********************
skipping: [odroid-hc2-00]

TASK [pcfe.housenet : YUM | ensure updates repo is disabled if on CentOS >= 7.0] ************************************
skipping: [odroid-hc2-00]

TASK [pcfe.housenet : YUM | ensure all security updates are applied if on CentOS >= 7.0] ****************************
skipping: [odroid-hc2-00]

TASK [pcfe.housenet : DNF | ensure repo fedora-updates-housenet is available if on Fedora >= 28] ********************
ok: [odroid-hc2-00]

TASK [pcfe.housenet : DNF | ensure updates repo is disabled if on Fedora >= 28] *************************************
ok: [odroid-hc2-00]

TASK [pcfe.housenet : DNF | ensure repo fedora-everything-housenet is enabled if on Fedora >= 28] *******************
ok: [odroid-hc2-00]

TASK [pcfe.housenet : DNF | ensure fedora repo is disabled if on Fedora >= 28] **************************************
ok: [odroid-hc2-00]

TASK [CHRONYD | ensure chrony-wait is enabled] **********************************************************************
ok: [odroid-hc2-00]

TASK [CHRONYD | ensure chronyd is enabled and running] **************************************************************
ok: [odroid-hc2-00]

TASK [JOURNAL | ensure persistent logging for the systemd journal is possible] **************************************
ok: [odroid-hc2-00]

TASK [SUDO | enable passwordless sudo for ansible user] *************************************************************
ok: [odroid-hc2-00]

PLAY RECAP **********************************************************************************************************
odroid-hc2-00              : ok=16   changed=0    unreachable=0    failed=0   


My Site-Specific Setup Playbook

After I created the bare minimum with arm-fedora-initial-setup.yml by running it once (I’m not going to needlessly run the task DNF | ensure all updates are applied, it takes a while), I have a bunch of local settings I like applied. They are only shown here because I reference arm-fedora-initial-setup.yml in another post.

My play odroid-general-setup.yml uses these roles:

  • linux-system-roles.network to set up static network configuration
    • new group name, update above
    • host_vars containing static network info, idem
  • pcfe.basic-security-setup sets non-negotiable settings
  • pcfe.housenet sets up timezone and the use of local package mirrors
  • pcfe.user_owner creates the user ‘pcfe’ (defined in group_vars/) and copies over in my ssh pubkey
  • pcfe.comfort to set up some bits that make my shell use more comfortable
  • pcfe.check_mk sets up to host for monitoring with my local Check_MK instance

And the following tasks:

  • implements a dirty work-around until check-mk-agent ≥ 1.5.0 is available in Fedora

  • installs language packs for languages I speak.

    • A nice side effect is that this stops dnf from complaining Failed to set locale, defaulting to C.
  • sets up the hardware watchdog.

  • sets up ODROID-HC2 tweaks

The play odroid-general-setup.yml is as follows

# sets up a Fedora 29 ARM minimal install with site-specific settings
# to be run AFTER odroid-initial-setup.yml RAN ONCE at least
# this is for my ODROID-HC2 boxes
- hosts:
  - odroids
  become: yes
  roles:
    - linux-system-roles.network
    - pcfe.basic-security-setup
    - pcfe.user_owner
    - pcfe.comfort
    - pcfe.check_mk

  # remove this Würgaround pre-task once 1.5.0 or later is available in Fedora repo
  pre_tasks:
    - name: "Ensure check-mk-agent-1.5.0p12-1.noarch.rpm is installed, because earlier versions have trouble with thermal zone output"
      dnf:
        name: 'http://check-mk.internal.pcfe.net/HouseNet/check_mk/agents/check-mk-agent-1.5.0p12-1.noarch.rpm'
        state: present
    - name: "ensure /usr/share/check-mk-agent exists"
      file:
        path: /usr/share/check-mk-agent
        state: directory
        mode: 0755
    - name: "symlink plugins and local from /usr/lib/check_mk_agent/ to /usr/share/check-mk-agent/"
      file:
        src: '/usr/lib/check_mk_agent/{{ item.src }}'
        dest: '/usr/share/check-mk-agent/{{ item.dest }}'
        state: link
      with_items:
        - { src: 'plugins', dest: 'plugins' }
        - { src: 'local', dest: 'local' }
    # That will only be necessary until "FEED-3415: linux smart plugin und JMicron USB nach SATA bridges" is fixed on Check_MK side
    - name: "ensure smart plugin is installed"
      template:
        src:    templates/ODROID-HC2/smart-for-check-mk.j2
        dest:   '/usr/lib/check_mk_agent/plugins/smart'
        group:  'root'
        mode:   '0755'
        owner:  'root'
  tasks:
#    # linux-system-roles.network sets static network config (from host_vars)
#    # but I want the static hostname nailed down too
#    # the below does not work though, try with ansible_fqdn instead
#    - name: "set hostname"
#      hostname:
#        name: '{{ ansible_hostname }}.internal.pcfe.net'

    # fix dnf's "Failed to set locale, defaulting to C" annoyance
    - name: "PACKAGE | ensure my preferred langpacks are installed"
      package:
        name:
          - langpacks-en
          - langpacks-en_GB
          - langpacks-de
          - langpacks-fr
        state: present

    # enable watchdog based on information from https://wiki.odroid.com/odroid-xu4/application_note/software/linux_watchdog
    # write watchdog kernel module config, this is needed to enable power cycle
    # alternatively one could use the kernel boot parameters, but I personally prefer modprobe.d/
    - name: "WATCHDOG | ensure kernel module s3c2410_wdt has correct options configured"
      lineinfile:
        path:         /etc/modprobe.d/s3c2410_wdt.conf
        create:       true
        regexp:       '^options '
        insertafter:  '^#options'
        line:         'options s3c2410_wdt tmr_margin=30 tmr_atboot=1 nowayout=0'

    # while testing, configure both watchdog.service and systemd watchdog, but only use the latter for now.
    - name: "PACKAGE | ensure watchdog package is installed"
      package:
        name:         watchdog
        state:        present
    - name: "WATCHDOG | ensure correct watchdog-device is used by watchdog.service"
      lineinfile:
        path:         /etc/watchdog.conf
        regexp:       '^watchdog-device'
        insertafter:  '^#watchdog-device'
        line:         'watchdog-device = /dev/watchdog'
    # values above 32 seconds do not work, cannot set timeout 33 (errno = 22 = 'Invalid argument')
    - name: "WATCHDOG | ensure timeout is set to 30 seconds for watchdog.service"
      lineinfile:
        path:         /etc/watchdog.conf
        regexp:       '^watchdog-timeout'
        insertafter:  '^#watchdog-timeout'
        line:         'watchdog-timeout = 30'

    # testing in progress;
    # Use systemd watchdog rather than watchdog.service
    - name: "WATCHDOG | Ensure watchdog.service is disabled"
      systemd:
        name:         watchdog.service
        state:        stopped
        enabled:      false
        
    # configure systemd watchdog
    # c.f. http://0pointer.de/blog/projects/watchdog.html
    - name: "SYSTEMD | ensure systemd watchdog is enabled"
      lineinfile:
        path:         /etc/systemd/system.conf
        regexp:       '^RuntimeWatchdogSec'
        insertafter:  'EOF'
        line:         'RuntimeWatchdogSec=30'
    - name: "SYSTEMD | ensure systemd shutdown watchdog is enabled"
      lineinfile:
        path:         /etc/systemd/system.conf
        regexp:       '^ShutdownWatchdogSec'
        insertafter:  'EOF'
        line:         'ShutdownWatchdogSec=30'

    # install and enable rngd
    - name: "PACKAGE | ensure rng-tools package is installed"
      package:
        name:         rng-tools
        state:        present
    - name: "RNGD | ensure rngd.service is enabled and started"
      systemd:
        name:         rngd.service
        state:        started
        enabled:      true

    # most tweaks taken from both 
    # https://forum.odroid.com/viewtopic.php?t=25424 and
    # https://magazine.odroid.com/wp-content/uploads/ODROID-Magazine-201702.pdf#ODROID%20Magazine%20Issue%2038.indd:.314673:59549
    - name: "ODROID-HC2 TWEAKS: ensure needed packages are installed"
      package:
        name:
          - libcgroup-tools
          - tuned
          - perl-interpreter
          - hdparm
          - tar
          - unzip
        state: present
    - name: "ODROID-HC2 TWEAKS: ensure odroid-cpu-control is available"
      # from https://raw.githubusercontent.com/mad-ady/odroid-cpu-control/master/odroid-cpu-control
      template:
        src:            templates/ODROID-HC2/odroid-cpu-control.j2
        dest:           /usr/local/bin/odroid-cpu-control
        mode:           '0755'
        owner:          root
        group:          root
    - name: "ODROID-HC2 TWEAKS: ensure cpuset.service is available"
      # from https://raw.githubusercontent.com/mad-ady/odroid-xu4-optimizations/master/cpuset.service
      template:
        src:            templates/ODROID-HC2/cpuset.service.j2
        dest:           /etc/systemd/system/cpuset.service
        mode:           '0644'
        owner:          root
        group:          root
    - name: "ODROID-HC2 TWEAKS: ensure cpuset.service is enabled"
      systemd:
        name:           cpuset.service
        enabled:        true
    - name: "ODROID-HC2 TWEAKS: ensure affinity.service is available"
      # from https://raw.githubusercontent.com/mad-ady/odroid-xu4-optimizations/master/affinity.service
      template:
        src:            templates/ODROID-HC2/affinity.service.j2
        dest:           /etc/systemd/system/affinity.service
        mode:           '0644'
        owner:          root
        group:          root
    - name: "ODROID-HC2 TWEAKS: ensure affinity.service is enabled"
      systemd:
        name:           affinity.service
        enabled:        true
    - name: "ODROID-HC2 TWEAKS: ensure tuned profile odroid directory exists"
      file:
        path:           /etc/tuned/odroid
        state:          directory
        mode:           '0755'
    - name: "ODROID-HC2 TWEAKS: ensure tuned config odroid is present"
      template:
        src:            templates/ODROID-HC2/tuned-profile-odroid.conf.j2
        dest:           /etc/tuned/odroid/tuned.conf
        mode:           '0644'
        group:          root
        owner:          root
    - name: "ODROID-HC2 TWEAKS: ensure tuned script odroid is present"
      template:
        src:            templates/ODROID-HC2/tuned-script-odroid.sh.j2
        dest:           /etc/tuned/odroid/script.sh
        mode:           '0755'
        group:          root
        owner:          root
    - name: "ODROID-HC2 TWEAKS: ensure tuned.service is enabled and running"
      systemd:
        name:           tuned.service
        state:          started
        enabled:        true
    - block:
      - name: "ODROID-HC2 TWEAKS: check which tuned profile is active"
        shell:          tuned-adm active
        register:       tuned_active_profile
        ignore_errors:  yes
        changed_when:   no
      - name: "ODROID-HC2 TWEAKS: activate tuned profile odroid"
        shell:          tuned-adm profile odroid
        when:           "tuned_active_profile.stdout.find('Current active profile: odroid') != 0"
    - block:
      - name: "ODROID-HC2 TWEAKS: ensure irqbalance is installed, since we set IRQ affinity to cores 4-7"
        package:
          name:
            - irqbalance
          state: present
      - name: "ODROID-HC2 TWEAKS: ensure irqbalance.service is enabled and started"
        systemd:
          name:         irqbalance.service
          state:        started
          enabled:      true
    - name: "ODROID-HC2 TWEAKS: ensure disk click at shutdown is fixed"
      # c.f. https://wiki.odroid.com/odroid-xu4/troubleshooting/shutdown_script
      # template is file from https://dn.odroid.com/5422/script/odroid.shutdown
      template:
        src:            templates/ODROID-HC2/odroid-disk.shutdown.j2
        dest:           /usr/lib/systemd/system-shutdown/odroid-disk.shutdown
        mode:           '0755'
        owner:          root
        group:          root
    - name: "ODROID-HC2 TWEAKS: make latest JMS578 Firmware updater available"
      get_url:
        url:            ftp://fileserver.internal.pcfe.net/pub/QNAP-Public/flash_images/Hardkernel/ODROID-HC2/JMS578_Firmware_updater/jms578fwupdater.tgz
        checksum:       'sha256:0e729256500ee70bb2caa91c584ff9dca06a262b7437c3b6a6529d5168b9a854'
        dest:           /root/jms578fwupdater.tgz
        mode:           '0644'
        owner:          root
        group:          root
    - name: "ODROID-HC2 TWEAKS: unarchive latest JMS578 Firmware updater"
      unarchive:
        remote_src:     yes
        src:            /root/jms578fwupdater.tgz
        dest:           /tmp/
    - name: "ensure logrotate and dnf-data are installed"
      package:
        name:
          - dnf-data
          - logrotate
        state: present
    - name: "ensure more agressve log rotation for dnf is in place"
      template:
        src:            templates/logrotate-dnf.j2
        dest:           /etc/logrotate.d/dnf
        mode:           '0644'
        owner:          root
        group:          root
    # https://wiki.odroid.com/odroid-xu4/software/disk_encryption
    # luckily ceph-ansible already sets up
    #   Cipher name:    aes
    #   Cipher mode:    xts-plain64
    #   Hash spec:      sha256
    # which has the highest performance


    # this is not yet working, revisit
    # while testing disk perf, just brute someting along the lines of
    # for i in `pgrep ceph` ; do taskset -c -p 4-7 $i ; done
    # cat /proc/956/task/*/status|grep Cpus_allowed_list
    # only use big cores (4-7) by adding to the relevant Service sections
    # ExecStartPost=-/bin/sh -c ‘echo $MAINPID | tee -a /sys/fs/cgroup/cpuset/bigcores/tasks’
    # - name: "SYSTEMD | CPUAffinity big cores only for all ceph-… services"
    #   lineinfile:
    #     path:         /etc/systemd/system/ceph-.service.d/ceph.conf
    #     create:       true
    #     regexp:       '^ExecStartPost='
    #     insertafter:  '^[Service]
    #     line:         'ExecStartPost=-/bin/sh -c `echo $MAINPID | tee -a /sys/fs/cgroup/cpuset/bigcores/tasks`'

Output of Site-Specific Setup Playbook

So that you can get an idea of what the roles I use do, here is the full log

ansible-playbook -i ../inventories/ceph-HouseNet-cluster.ini -l odroid-hc2-00 --skip-tags apply_errata odroid-general-setup.yml | tee ../../hugo-blog.pcfe.net/static/code/odroid-general-setup.log

(In this example, I skip applying errata in order to get this log output quicker)


PLAY [odroids] ********************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************
ok: [odroid-hc2-04]

TASK [Ensure check-mk-agent-1.5.0p12-1.noarch.rpm is installed, because earlier versions have trouble with thermal zone output] ***
changed: [odroid-hc2-04]

TASK [ensure /usr/share/check-mk-agent exists] ************************************************************************
changed: [odroid-hc2-04]

TASK [symlink plugins and local from /usr/lib/check_mk_agent/ to /usr/share/check-mk-agent/] **************************
changed: [odroid-hc2-04] => (item={'src': 'plugins', 'dest': 'plugins'})
changed: [odroid-hc2-04] => (item={'src': 'local', 'dest': 'local'})

TASK [ensure smart plugin is installed] *******************************************************************************
changed: [odroid-hc2-04]

TASK [linux-system-roles.network : Check which services are running] **************************************************
ok: [odroid-hc2-04]

TASK [linux-system-roles.network : Check which packages are installed] ************************************************
ok: [odroid-hc2-04]

TASK [linux-system-roles.network : Print network provider] ************************************************************
ok: [odroid-hc2-04] => {
    "msg": "Using network provider: nm"
}

TASK [linux-system-roles.network : Install packages] ******************************************************************
changed: [odroid-hc2-04]

TASK [linux-system-roles.network : Enable network service] ************************************************************
ok: [odroid-hc2-04]

TASK [linux-system-roles.network : Configure networking connection profiles] ******************************************
changed: [odroid-hc2-04]

TASK [linux-system-roles.network : Re-test connectivity] **************************************************************
ok: [odroid-hc2-04]

TASK [pcfe.basic-security-setup : BASIC SEC | ensure selinux is running with enforcing] *******************************
ok: [odroid-hc2-04]

TASK [pcfe.basic-security-setup : BASIC SEC | ensure ssh auth is via ssh-key only] ************************************
ok: [odroid-hc2-04]

TASK [pcfe.user_owner : USER OWNER | ensure group pcfe exists] ********************************************************
changed: [odroid-hc2-04]

TASK [pcfe.user_owner : USER OWNER | ensure user pcfe exists] *********************************************************
changed: [odroid-hc2-04]

TASK [pcfe.user_owner : USER OWNER | ensure authorized key for pcfe exists] *******************************************
changed: [odroid-hc2-04]

TASK [pcfe.user_owner : USER OWNER | ensure authorized key for root exists] *******************************************
ok: [odroid-hc2-04]

TASK [pcfe.comfort : COMFORT | ensure packages for comfortable shell use are installed] *******************************
changed: [odroid-hc2-04]

TASK [pcfe.comfort : COMFORT | on Fedora, also ensure fortune is installed] *******************************************
changed: [odroid-hc2-04]

TASK [pcfe.comfort : BASH | my additions for pcfe .bashrc] ************************************************************
changed: [odroid-hc2-04]

TASK [pcfe.comfort : BASH | my additions for pcfe .bash_profile] ******************************************************
changed: [odroid-hc2-04]

TASK [pcfe.comfort : BASH | my additions for root .bashrc] ************************************************************
changed: [odroid-hc2-04]

TASK [pcfe.comfort : BASH | my additions for root .bash_profile] ******************************************************
changed: [odroid-hc2-04]

TASK [pcfe.comfort : SCREEN | fixup /etc/screenrc] ********************************************************************
changed: [odroid-hc2-04]

TASK [pcfe.check_mk : MONITORING | EL7, ensure firewalld and its python module are installed] *************************
skipping: [odroid-hc2-04]

TASK [pcfe.check_mk : MONITORING | Fedora, ensure firewalld and its python module are installed] **********************
ok: [odroid-hc2-04]

TASK [pcfe.check_mk : MONITORING | ensure non VM packages for monitoring are installed] *******************************
changed: [odroid-hc2-04]

TASK [pcfe.check_mk : MONITORING | ensure common packages for monitoring are installed] *******************************
changed: [odroid-hc2-04]

TASK [pcfe.check_mk : MONITORING | EL, ensure firewalld permits 6556 for check-mk-agent] ******************************
skipping: [odroid-hc2-04]

TASK [pcfe.check_mk : MONITORING | Fedora, ensure firewalld permits 6556 in zone public for check-mk-agent] ***********
changed: [odroid-hc2-04]

TASK [pcfe.check_mk : MONITORING | ensure tarsnap cache is in fileinfo] ***********************************************
changed: [odroid-hc2-04]

TASK [pcfe.check_mk : MONITORING | ensure entropy_avail plugin for Check_MK is present] *******************************
changed: [odroid-hc2-04]

TASK [pcfe.check_mk : MONITORING | non-VM ensure used plugins are enabled in check-mk-agent by setting symlink] *******
skipping: [odroid-hc2-04] => (item={'src': 'smart', 'dest': 'smart'}) 

TASK [pcfe.check_mk : MONITORING | common ensure used plugins are enabled in check-mk-agent by setting symlink] *******
skipping: [odroid-hc2-04] => (item={'src': 'lvm', 'dest': 'lvm'}) 
skipping: [odroid-hc2-04] => (item={'src': 'mk_inventory', 'dest': 'mk_inventory'}) 

TASK [pcfe.check_mk : MONITORING | Ensure check_mk.socket is started and enabled] *************************************
changed: [odroid-hc2-04]

TASK [PACKAGE | ensure my preferred langpacks are installed] **********************************************************
changed: [odroid-hc2-04]

TASK [WATCHDOG | ensure kernel module s3c2410_wdt has correct options configured] *************************************
changed: [odroid-hc2-04]

TASK [PACKAGE | ensure watchdog package is installed] *****************************************************************
changed: [odroid-hc2-04]

TASK [WATCHDOG | ensure correct watchdog-device is used by watchdog.service] ******************************************
changed: [odroid-hc2-04]

TASK [WATCHDOG | ensure timeout is set to 30 seconds for watchdog.service] ********************************************
changed: [odroid-hc2-04]

TASK [WATCHDOG | Ensure watchdog.service is disabled] *****************************************************************
ok: [odroid-hc2-04]

TASK [SYSTEMD | ensure systemd watchdog is enabled] *******************************************************************
changed: [odroid-hc2-04]

TASK [SYSTEMD | ensure systemd shutdown watchdog is enabled] **********************************************************
changed: [odroid-hc2-04]

TASK [ODROID-HC2 TWEAKS: ensure needed packages are installed] ********************************************************
changed: [odroid-hc2-04]

TASK [ODROID-HC2 TWEAKS: ensure odroid-cpu-control is available] ******************************************************
changed: [odroid-hc2-04]

TASK [ODROID-HC2 TWEAKS: ensure cpuset.service is available] **********************************************************
changed: [odroid-hc2-04]

TASK [ODROID-HC2 TWEAKS: ensure cpuset.service is enabled] ************************************************************
changed: [odroid-hc2-04]

TASK [ODROID-HC2 TWEAKS: ensure affinity.service is available] ********************************************************
changed: [odroid-hc2-04]

TASK [ODROID-HC2 TWEAKS: ensure affinity.service is enabled] **********************************************************
changed: [odroid-hc2-04]

TASK [ODROID-HC2 TWEAKS: ensure tuned profile odroid directory exists] ************************************************
changed: [odroid-hc2-04]

TASK [ODROID-HC2 TWEAKS: ensure tuned config odroid is present] *******************************************************
changed: [odroid-hc2-04]

TASK [ODROID-HC2 TWEAKS: ensure tuned script odroid is present] *******************************************************
changed: [odroid-hc2-04]

TASK [ODROID-HC2 TWEAKS: ensure tuned.service is enabled and running] *************************************************
fatal: [odroid-hc2-04]: FAILED! => {"changed": false, "msg": "Unable to start service tuned.service: Job for tuned.service failed because a fatal signal was delivered causing the control process to dump core.\nSee \"systemctl status tuned.service\" and \"journalctl -xe\" for details.\n"}

PLAY RECAP ************************************************************************************************************
odroid-hc2-04              : ok=49   changed=38   unreachable=0    failed=1   


My Templates

While most of the template files are from the thread XU4 4.9 kernel, NAS, Webmin, Owncloud and tweaks or ODROID-Magazine Feb 2017, I prefer to have them available locally. The tuned templates and the tiny change to check-mk-agent’s smart plugin are my own.

affinity.service.j2

from mad-ady/odroid-xu4-optimizations

[Unit]
Description=Setup special irq affinity
Before=sysinit.target
After=local-fs.target
DefaultDependencies=no

[Service]
Type=oneshot
ExecStart=/bin/true
ExecStartPost=-/bin/sh -c 'for i in `cat /proc/interrupts | grep usb | cut -d ":" -f 1`; do echo "4-7" > /proc/irq/$i/smp_affinity_list; done'

[Install]
WantedBy=sysinit.target

cpuset.service.j2

from mad-ady/odroid-xu4-optimizations

[Unit]
Description=Setup big/little cgroups
Before=sysinit.target
After=local-fs.target
DefaultDependencies=no

[Service]
Type=oneshot
ExecStart=/bin/true
ExecStartPost=-/bin/mkdir -p /sys/fs/cgroup/cpuset/littlecores /sys/fs/cgroup/cpuset/bigcores
ExecStartPost=-/bin/sh -c '/bin/echo "0-3" > /sys/fs/cgroup/cpuset/littlecores/cpuset.cpus'
ExecStartPost=-/bin/sh -c '/bin/echo "0"> /sys/fs/cgroup/cpuset/littlecores/cpuset.mems'
ExecStartPost=-/bin/sh -c '/bin/chmod -R 777 /sys/fs/cgroup/cpuset/littlecores'
ExecStartPost=-/bin/sh -c '/bin/echo "4-7"> /sys/fs/cgroup/cpuset/bigcores/cpuset.cpus'
ExecStartPost=-/bin/sh -c '/bin/echo "0"> /sys/fs/cgroup/cpuset/bigcores/cpuset.mems'
ExecStartPost=-/bin/sh -c '/bin/chmod -R 777 /sys/fs/cgroup/cpuset/bigcores'

[Install]
WantedBy=sysinit.target

odroid-cpu-control.j2

from mad-ady/odroid-cpu-control

#!/usr/bin/env perl
use strict;
#use warnings;
use Getopt::Long qw(:config no_ignore_case);
use Data::Dumper;

#
# Code released under GPLv3: http://www.gnu.org/licenses/gpl.html
# v0.3 - Adrian Popa (2016.03.11) 
#

#we get our data mainly from /sys/devices/system/cpu

my %options = ();

my $cls = "\033[2J\033[0;0H"; #fancy ANSI escape string

GetOptions(\%options, 'help|h', 'quiet|q', 'list|l', 'set|s', 'governor|g:s', 'cpu|c=s', 'min|m:s', 'max|M:s', 'frequency|f', 'interactive|i=f', 'temperature|t');
#print Dumper(\%options);

if(defined $options{'help'}){
    usage();
    exit(0);
}

if(defined $options{'set'}){
    #interpret the parameters as a set command
    
    #user must be root
    die "The script must be run as root in order to apply changes!" if($> != 0);
    
    #see on which cpus you need to set things
    my @cpu = getCPUs();
    my %before;
    if(! exists $options{'quiet'}){
        %before = getListing(@cpu);
    }
    
    #set the data
    foreach my $core (@cpu){
        if(defined $options{'governor'}){
            open FILE, ">/sys/devices/system/cpu/cpu${core}/cpufreq/scaling_governor" or die "$!";
            print FILE $options{'governor'};
            close FILE;
        }
        if(defined $options{'min'}){
            open FILE, ">/sys/devices/system/cpu/cpu${core}/cpufreq/scaling_min_freq" or die "$!";
            my $number = $options{'min'};
            if($options{'min'}=~/([0-9\.]+)(M|G)?/){
                $number = $1;
                my $scale = $2;
                $number = $number * 1000 if($scale eq 'M');
                $number = $number * 1000_000 if($scale eq 'G');
            }
            print FILE $number;
            close FILE;
        }
        if(exists $options{'max'}){
            open FILE, ">/sys/devices/system/cpu/cpu${core}/cpufreq/scaling_max_freq" or die "$!";
            my $number = $options{'max'};
            if($options{'max'}=~/([0-9\.]+)(M|G)?/){
                $number = $1;
                my $scale = $2;
                #print "DBG: $options{'max'}\n";
                $number = $number * 1000 if(defined $scale && $scale eq 'M');
                $number = $number * 1000_000 if(defined $scale && $scale eq 'G');
            }
            print FILE $number;
            close FILE;
        }
        if(exists $options{'frequency'}){
            #can't set current frequency
        }
    }
    
    #display results (if any)
    if(! exists $options{'quiet'}){
        my %after = getListing(@cpu);
#        print "DBG: ".Dumper(\%after);
        foreach my $core (@cpu){
            
            if(defined $after{$core}{'governor'}){
                print "CPU$core: governor $before{$core}{'governor'} -> $after{$core}{'governor'}\n";
            }
            if(defined $after{$core}{'scale_min'}){
                print "CPU$core: min ".humanReadable($before{$core}{'scale_min'})." [".humanReadable($before{$core}{'cpuinfo_min'}) ."] -> ".humanReadable($after{$core}{'scale_min'})." [".humanReadable($after{$core}{'cpuinfo_min'}) ."]\n";
            }
            if(defined $after{$core}{'scale_max'}){
                print "CPU$core: max ".humanReadable($before{$core}{'scale_max'})." [".humanReadable($before{$core}{'cpuinfo_max'}) ."] -> ".humanReadable($after{$core}{'scale_max'})." [".humanReadable($after{$core}{'cpuinfo_max'}) ."]\n";
            }
        }
    }
    
}
elsif(defined $options{'list'}){
    #select all things to display if none is specified
    if(! exists $options{'governor'} && ! exists $options{'min'} && ! exists $options{'max'} && ! exists $options{'frequency'} && ! exists $options{'temperature'}){
        $options{'governor'} = undef;
        $options{'min'} = undef;
        $options{'max'} = undef;
        $options{'frequency'} = undef;
    $options{'temperature'} = undef;
    }
    #start an infinte loop in case we're in interactive mode
    while(1){
        my @cpu = getCPUs();
    
        #make output buffered
        $|=0;
    
    if(defined $options{'interactive'}){
        print $cls;
        print "Type CTRL+C to exit.\n";
    }
        
    my $temp = 0;
    if(defined $options{'temperature'}){
        $temp = getTemperature();
    }

    #print Dumper(\%results);
    if(exists $options{'governor'} || exists $options{'frequency'} || exists $options{'min'} || exists $options{'max'}){
        my %results = getListing(@cpu);
        #display the data -- redundant for, but we might expand/decouple the display later
    foreach my $core (@cpu){
        
        my $output.= "CPU$core: ";
            $output.="governor $results{$core}{'governor'}\t" if (exists $results{$core}{'governor'});
            $output.="current ".humanReadable($results{$core}{'cur'})."\t" if (exists $results{$core}{'cur'});
            $output.="min ".humanReadable($results{$core}{'scale_min'})." [".humanReadable($results{$core}{'cpuinfo_min'}) ."]\t" if (exists $results{$core}{'scale_min'});
            $output.="max ".humanReadable($results{$core}{'scale_max'})." [".humanReadable($results{$core}{'cpuinfo_max'}) ."] \t" if (exists $results{$core}{'scale_max'});
        
        
            print $output."\n";
    }
    }
    if(defined $options{'temperature'}){
        print "TEMP: ${temp}C\n";
    }

    if(defined $options{'interactive'}){
        #sleep($options{'interactive'});
        #sleep fractional seconds without the need of Time::HiRes
        select(undef, undef, undef, $options{'interactive'});
    }
    else{
        last; #break the infinite while
    }
    $|=1;
    }
}
else{
    usage();
    exit(1);
}

sub humanReadable{
    my $value = shift;
    my %scaleMapping = (
        '3' => 'MHz',
        '6' => 'GHz',
        '9' => 'THz'
    );
    
    my $result = $value;
    my $scale = 0;
    my $break = 0;
    while($result >= 1000){
        $result = $result/1000;
        $scale+=3;
    }
    return sprintf("%.2f", $result) . $scaleMapping{$scale};
}

sub usage{
    my $governors = getData("/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors");
    print "Usage:
$0 [options]

Options:
 -l, --list\t\t\t List a parameter
 -s, --set\t\t\t Set a parameter
 -g, --governor <governor>\t Select a governor
 \t\t\t\t ($governors)
 -c, --cpu <number|range>\t The CPU to edit/query. Leave blank for all CPUs. Valid syntax:
 \t\t\t\t 0; 0,4; 0,4,5-7; 0-7
 -m, --min <number>\t\t The minimum CPU frequency (must be supported by governor and CPU)
 -M, --max <number>\t\t The maximum CPU frequency (must be supported by governor and CPU)
 -f, --frequency\t\t The current CPU frequency
 -t, --temperature\t\t The current CPU temperature
 -q, --quiet\t\t\t Don't display much output when setting a parameter
 -i, --interactive <number>\t Keep running the list command every number seconds
 -h, --help\t\t\t Show this help screen
 
Examples:
 $0 -l\t\t\t\tLists current frequency/governor for all cores
 $0 -l -g -f\t\t\t\tLists just governor and current frequency for all cores
 $0 -l -c 0,4\t\t\t\tLists all information only for cores 0 and 4
 $0 -l -c 0-4\t\t\t\tLists all information for cores 0 to 4
 $0 -s -g powersave -m 300M -M 600M\tSets governor, minimum and maximum frequency for all cores
 $0 -s -M 1.2G -c 0,4\t\t\tSets maximum frequency on cores 0 and 4
"
}

sub getCPUs{
    my @cpu = ();
    #see if the user specified a CPU
    if(defined $options{'cpu'}){
        #parse the CPU option. Could be a number or a sequence
        # -c 1
        # -c 1,4
        # -c 1,4,5-8
        my @commas = split(/,/, $options{'cpu'});
        foreach my $comma (@commas){
            #see if there are any ranges
            if($comma=~/([0-9]+)-([0-9]+)/){
                my $start = $1;
                my $stop = $2;
                for($start..$stop){
                    push @cpu, $_;
                }
            }
            else{
                push @cpu, $comma;
            }
        }
        #push @cpu, $options{'cpu'};
    }
    else{
        #get all cpus from the system
        my $content = getData("/sys/devices/system/cpu/online");
        if($content=~/([0-9]+)(?:-([0-9]+))?/){
            my $minCPU = $1;
        my $maxCPU = $minCPU;
        $maxCPU = $2 if(defined $2);
            for($minCPU..$maxCPU){
                push @cpu, $_;
            }
        }
        else{
            die "Unable to get the list of CPUs";
        }
    }
    return @cpu;
}

sub getData{
    my $filename = shift;
    my $line = 'N/A';
    if(open FILE, "$filename"){
        $line = <FILE>;
        $line=~s/\r|\n//g;
        close FILE;
    }
    return $line;
}

sub getTemperature{
    my $temperatureFile = '/sys/devices/virtual/thermal/thermal_zone0/temp';
    my $temp = 0;
    if(-f "$temperatureFile"){
    $temp = getData($temperatureFile);
    $temp = $temp/1000;
    }
    return $temp;
}

sub getListing{
    my %results;
    my @cpu = @_;
    
    #get the data
    foreach my $core (@cpu){
        if(exists $options{'governor'}){
            $results{$core}{'governor'} = getData("/sys/devices/system/cpu/cpu${core}/cpufreq/scaling_governor");
        }
        if(exists $options{'min'}){
            $results{$core}{'scale_min'} = getData("/sys/devices/system/cpu/cpu${core}/cpufreq/scaling_min_freq");
            $results{$core}{'cpuinfo_min'} = getData("/sys/devices/system/cpu/cpu${core}/cpufreq/cpuinfo_min_freq");
        }
        if(exists $options{'max'}){
            $results{$core}{'scale_max'} = getData("/sys/devices/system/cpu/cpu${core}/cpufreq/scaling_max_freq");
            $results{$core}{'cpuinfo_max'} = getData("/sys/devices/system/cpu/cpu${core}/cpufreq/cpuinfo_max_freq");
        }
        if(exists $options{'frequency'}){
            $results{$core}{'cur'} = getData("/sys/devices/system/cpu/cpu${core}/cpufreq/scaling_cur_freq");
        }
    }
    
    return %results;
}

odroid-disk.shutdown.j2

from the ODROID Wiki

#!/bin/bash
exec </dev/null </dev/null 2>/dev/null
export LANG=C LC_ALL=C

###
### actually Fedora's mdadm already drops a 
### /usr/lib/systemd/system-shutdown/mdadm.shutdown
### that does that part.
### # In all cases, we want the media to be in quiescent, clean state.
### sync
### [ -x /sbin/mdadm ] && /sbin/mdadm --wait-clean --scan
### Note though that:
### All executables in this directory are executed in parallel, and execution of the action is not continued before all executables finished.

# updated script taken from https://forum.odroid.com/viewtopic.php?f=97&t=29069&start=50#p246051
systemctl list-jobs | egrep -q 'reboot.target.*start' && yval='Y' || yval='y'

# Function used to park all SATA disks.
function ParkDisks() {
    if [ -x /sbin/hdparm ]; then
        Wait=0
        for Dev in /sys/block/sd* ; do
            [ -e $Dev ] && /sbin/hdparm -$yval /dev/${Dev##*/} && Wait=5
            sleep $Wait
            echo 1 > /sys/class/block/${Dev##*/}/device/delete
        done
        sleep $Wait
    fi
}

case "$1" in
    # reboot|kexec)
        # Do not park disks when rebooting or switching kernels.
    #     ;;
    *)
        ParkDisks
        ;;
esac

smart-for-check-mk.j2

The change below is only necessary until “FEED-3415: linux smart plugin und JMicron USB nach SATA bridges” is implemented on the Check_MK side

--- smart       2019-02-19 20:45:56.477618050 +0100
+++ /root/smart 2019-02-19 20:47:37.678256369 +0100
@@ -142,6 +142,9 @@
             MODEL=${MODEL// /-}
             DNAME=${DNAME#AMCC_}
             DNAME="AMCC_${MODEL}_${DNAME%000000000000}"
+       # 2019-02-19 Patrick C. F. Ernzer - special option in case vendor is JMicron
+       elif [ "$VEND" == "JMicron" ]; then
+               CMD="smartctl -d sat -v 9,raw48 -A $D"
         elif [ "$VEND" != "ATA" ] ; then
             TEMP=
             # create temperature output as expected by checks/smart

tuned-profile-odroid.conf.j2

#
# tuned configuration
#

[main]
summary=ODROID-HC2 tuned profile

[cpu]
# this and 2 sysfs settings as per
# ODROID Magazine Feb 2017
# General-Purpose NAS
governor=ondemand
sampling_down_factor=10

[net]
# Wake-on-lan
# d   Disable (wake on  nothing).
wake_on_lan=d
# automatic speed change disabled
dynamic_tuning=false

[systemd_disk]
type=disk
devices=mmcblk0
#disable_barriers=false

[data_disk]
type=disk
devices=sda
#disable_barriers=false

[usb]
# Since both Network and SATA are on USB3,
# do not allow USB autosuspend
autosuspend=-1

[sysfs]
/sys/devices/system/cpu/cpufreq/ondemand/io_is_busy=1
/sys/devices/system/cpu/cpufreq/ondemand/up_threshold=80
/sys/bus/usb/devices/usb4/power/control=on
/sys/bus/usb/devices/usb6/power/control=on

tuned-script-odroid.sh.j2

#!/bin/sh

. /usr/lib/tuned/functions

start() {

        return 0
}

stop() {

        return 0
}

process $@