Merge pull request #1 from glennklockwood/local

operate in local (pull) mode
master
Glenn K. Lockwood 6 years ago committed by GitHub
commit ec8c365094
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .gitignore
  2. 54
      README.md
  3. 4
      hosts
  4. 7
      local.yml
  5. 3
      roles/common/handlers/main.yml
  6. 25
      roles/common/tasks/linux-facts.yml
  7. 179
      roles/common/tasks/main.yml
  8. 98
      roles/common/tasks/raspi-config.yml
  9. 221
      roles/common/tasks/raspi-facts.yml
  10. 45
      roles/common/tasks/software.yml
  11. 41
      roles/common/tasks/users.yml
  12. 47
      roles/common/vars/main.yml
  13. 4
      site.yml

2
.gitignore vendored

@ -1 +1 @@
site.retry
local.retry

@ -1,52 +1,46 @@
# Raspberry Pi Ansible
Glenn K. Lockwood, August 2017
Glenn K. Lockwood, October 2018
## Introduction
This is an Ansible configuration that configures a fresh Raspbian installation
on Raspberry Pi. This is very much a work in progress and not intended to be
used by anyone but me.
on Raspberry Pi. It is intended to be run in local (pull) mode, where ansible
is running on the same Raspberry Pi to be configured.
## Bootstrapping on Raspbian
If you want to use these playbooks to make a Raspberry Pi self-configure,
install Ansible by doing the following:
You will need ansible installed on the Raspberry Pi being configured.
$ pip install --user ansible
$ ssh-keygen
$ ssh-copy-id localhost
$ sudo apt-get install ansible
If not bootstrapping from the Raspberry Pi itself, you can instead do
## Configuration
$ ssh-copy-id pi@raspberrypi
The `macaddrs` structure in _roles/common/vars/main.yml_ maps the MAC address of
a Raspberry Pi to its intended configuration state. Add your Raspberry Pi's MAC
address to that structure and set its configuration accordingly.
and authenticate using the default `raspberry` password. This will enable
key-based authentication to the remote Raspberry Pi to be configured.
## Running the playbook
You can ensure that Ansible is able to configure using the following:
$ ansible -i hosts all -m ping
You can also ensure that authentication also works.
Then run the playbook:
$ ansible -i hosts -u pi --sudo-user root all -a "/usr/bin/id -u"
$ sudo ansible-playbook local.yml
## Running the Playbook
The playbook will self-discover its settings, then idempotently configure the
Raspberry Pi.
This playbook will deactivate password authentication for the `pi` user since
it assumes that you have key-based authentication configured _before_ the
playbook is executed. Be sure that is the case or you may be locked out of
your Raspberry Pi altogether.
## After running the playbook
Then run the playbook:
This playbook purposely requires a few manual steps _after_ running the playbook
to ensure that it does not lock you out of your Raspberry Pi.
$ ansible-playbook --inventory-file hosts --limit cloverfield --user pi --sudo site.yml
1. While logged in as pi, `sudo passwd glock` (or whatever username you created)
to set a password for that user. This is _not_ required to log in as that
user, but it _is_ required to `sudo` as that user. You may also choose to
set a password for the pi and/or root users.
or
2. `usermod --lock pi` to ensure that the default user is completely disabled.
$ ansible-playbook -i hosts -l clovermine -u pi -s site.yml
## Acknowledgment
Raspbian should allow the `pi` user to sudo without a password. If not, run
using `--ask-become-pass` (or `-K`) and enter the sudo password (default would
be `raspberry`) for the remote user (`pi`).
I stole a lot of knowledge from https://github.com/giuaig/ansible-raspi-config/.

@ -1,3 +1 @@
cloverfield ansible_host=192.168.1.153
clovermine ansible_host=192.168.1.154
clovermill ansible_host=192.168.1.149
localhost ansible_connection=local

@ -0,0 +1,7 @@
---
- name: Raspberry Pi self configuration
hosts: localhost
user: root
connection: local
roles:
- common

@ -1,3 +0,0 @@
---
- name: changed timezone
command: dpkg-reconfigure --frontend noninteractive tzdata

@ -0,0 +1,25 @@
---
- name: get timezone via timedatectl
shell: "timedatectl | grep 'Time zone' | cut -d':' -f2 | cut -d'(' -f1 | sed -Ee 's/(^ *| *$)//g'"
register: linux_tz
changed_when: False
check_mode: no
- name: get locale
shell: "locale | grep ^LANG | cut -d= -f2"
register: linux_locale
changed_when: False
check_mode: no
- name: get x keyboard layout
shell: "localectl | awk '/X11 Layout/ {print $3}'"
register: linux_xkblayout
changed_when: False
check_mode: no
- name: set linux-config facts
set_fact:
linux_tz: "{{ linux_tz.stdout }}"
linux_locale: "{{ linux_locale.stdout }}"
linux_xkblayout: "{{ linux_xkblayout.stdout }}"
check_mode: no

@ -1,26 +1,40 @@
---
### Switch to non-default user as soon as possible if possible
#- name: does primary login user exist?
# local_action: "command ssh -q -o ConnectTimeout=3 -l {{ create_users[0].name }} {{ inventory_hostname }} /bin/true"
# register: user_exists
# ignore_errors: true
# changed_when: false
#
#- name: switch remote_user if possible
# remote_user: "{{ user_exists | success | ternary(omit, create_users[0].name) }}"
# command: "/bin/true"
# changed_when: false
### Set hostname
# Gather facts specific to the Raspberry Pi platform
- include: raspi-facts.yml
- include: linux-facts.yml
# Basic hostname setup
- name: Get MAC address
debug:
msg: "{{ hostvars[inventory_hostname].ansible_default_ipv4.macaddress }}"
tags:
- raspi
- sw
- name: store MAC address
set_fact:
my_macaddr: "{{ hostvars[inventory_hostname].ansible_default_ipv4.macaddress }}"
tags:
- raspi
- sw
- name: store system configuration
set_fact:
myconfig: "{{ macaddrs[my_macaddr] }}"
tags:
- raspi
- sw
- name: set hostname
hostname: name={{ inventory_hostname }}
when: inventory_hostname is defined and ansible_nodename is defined
shell: "raspi-config nonint do_hostname {{ myconfig.hostname }}"
when: raspi_hostname != myconfig.hostname
- name: update /etc/hosts with new hostname
lineinfile:
dest=/etc/hosts
regexp="^{{ ansible_default_ipv4.address }}"
line="{{ ansible_default_ipv4.address }}{{'\t'}}{{ inventory_hostname }}.local{{'\t'}}{{ inventory_hostname }}"
line="{{ ansible_default_ipv4.address }}{{'\t'}}{{ myconfig.hostname }}.{{ myconfig.domain }}{{'\t'}}{{ myconfig.hostname }}"
state=present
- name: get rid of default 127.0.1.1 binding
@ -29,78 +43,65 @@
regexp="^127.0.1.1"
state=absent
### Configure /etc/hosts
- name: ensure that all local hosts are in /etc/hosts
# Set timezone
- name: set timezone
command: "timedatectl set-timezone {{ myconfig.timezone }}"
when: linux_tz != myconfig.timezone
# Set locale
- name: set locale
command: "raspi-config nonint do_change_locale {{ myconfig.locale }}"
when: "'locale' in myconfig and linux_locale != myconfig.locale"
# Set X keyboard layout
- name: set X11 keyboard layout
command: "raspi-config nonint do_configure_keyboard {{ myconfig.xkblayout }}"
when: "'xkblayout' in myconfig and myconfig.xkblayout != linux_xkblayout"
# Set wifi country
- name: set wifi country
command: "raspiconfig nonint do_wifi_country {{ myconfig.wifi_country }}"
when: "'wifi_country' in myconfig and myconfig.wifi_country != raspi_wifi_country"
# Enable sshd
- name: disable ssh login for user pi
lineinfile:
dest=/etc/hosts
line="{{ item.ip }}{{'\t'}}{{ item.name }}.local{{'\t'}}{{ item.name }}"
state=present
with_items: "{{etc_hosts_contents}}"
### Set timezone
- name: set /etc/timezone to America/Los_Angeles
copy: src=etc/timezone
dest=/etc/timezone
owner=root
group=root
mode=0644
backup=yes
notify:
- changed timezone
### Uninstall Raspbian bloat
- name: remove raspbian bloat
apt:
name="{{ item }}"
state=absent
with_items:
- wolfram-engine
- libreoffice*
- scratch
- minecraft-pi
- python-minecraftpi
- python3-minecraftpi
- sonic-pi
- dillo
- gpiciew
- penguinspuzzle
### Install required software
- name: install basic software environment
apt:
name="{{ item }}"
dest=/etc/ssh/sshd_config
line="DenyUsers pi"
state=present
update_cache=yes
with_items:
- vim
- git
- python-pip
### Create user accounts
- name: create users
user: name="{{ item.name }}"
comment="{{ item.comment }}"
group="{{ item.group }}"
groups="{{ item.groups }}"
uid="{{ item.uid }}"
state=present
shell=/bin/bash
with_items: "{{ create_users }}"
tags: [ 'users' ]
- name: install ssh pubkeys for new users
authorized_key: user="{{ item.name }}"
key="{{ item.pubkey }}"
state=present
with_items: "{{ create_users }}"
tags: [ 'users' ]
### disable the 'pi' user's ability to login in with password
### if you enable this, you may lock yourself out--you must make sure another
### user has been added with both sudo privileges and a password by which
### sudo can be authenticated
#- name: disable 'pi' user
# user: name="pi"
# password="*"
# state=present
# tags: [ 'users' ]
tags:
- raspi
- name: enable SSH via raspi-config
shell: "raspi-config nonint do_ssh 0"
when: not raspi_ssh_enabled
tags:
- raspi
# Other tasks
- include: software.yml
- include: users.yml
- include: raspi-config.yml
# Configure firewall
- name: allow SSH through UFW
ufw:
rule: allow
port: ssh
proto: tcp
log: yes
- name: set default incoming UFW policy to deny
ufw:
direction: incoming
policy: deny
- name: set default outgoing UFW policy to deny
ufw:
direction: outgoing
policy: allow
- name: enable UFW
ufw:
state: enabled
logging: yes

@ -0,0 +1,98 @@
---
# Handle boot and autologin settings
- name: enable cli only
command: "raspi-config nonint do_boot_behaviour B1"
when: not myconfig.enable_gui and not myconfig.enable_autologin and (raspi_gui_enabled or raspi_autologin_enabled)
tags:
- raspi
- name: enable cli with autologin
command: "raspi-config nonint do_boot_behaviour B2"
when: not myconfig.enable_gui and myconfig.enable_autologin and (raspi_gui_enabled or not raspi_autologin_enabled)
tags:
- raspi
- name: enable desktop gui
command: "raspi-config nonint do_boot_behaviour B3"
when: myconfig.enable_gui and not myconfig.enable_autologin and (not raspi_gui_enabled or raspi_autologin_enabled)
tags:
- raspi
- name: enable desktop gui with autologin
command: "raspi-config nonint do_boot_behaviour B4"
when: myconfig.enable_gui and myconfig.enable_autologin and (not raspi_gui_enabled or raspi_autologin_enabled)
tags:
- raspi
- name: set bootwait option
command: "raspi-config nonint do_boot_wait {{ 0 if myconfig.enable_bootwait else 1 }}"
when: "'enable_bootwait' in myconfig and myconfig.enable_bootwait != raspi_bootwait_enabled"
tags:
- raspi
- name: set boot splash option
command: "raspi-config nonint do_boot_splash {{ 0 if myconfig.enable_bootsplash else 1 }}"
when: "'enable_bootsplash' in myconfig and myconfig.enable_bootsplash != raspi_bootsplash_enabled"
tags:
- raspi
- name: enable/disable camera
command: "raspi-config nonint do_camera {{ 0 if myconfig.enable_camera else 1 }}"
when: "'enable_camera' in myconfig and myconfig.enable_camera != raspi_camera_enabled"
tags:
- raspi
- name: enable/disable VNC server
command: "raspi-config nonint do_vnc {{ 0 if myconfig.enable_vnc else 1 }}"
when: "'enable_vnc' in myconfig and myconfig.enable_vnc != raspi_vnc_enabled"
tags:
- raspi
- name: enable/disable SPI
command: "raspi-config nonint do_spi {{ 0 if myconfig.enable_spi else 1 }}"
when: "'enable_spi' in myconfig and myconfig.enable_spi != raspi_spi_enabled"
tags:
- raspi
- name: enable/disable I2C
command: "raspi-config nonint do_i2c {{ 0 if myconfig.enable_i2c else 1 }}"
when: "'enable_i2c' in myconfig and myconfig.enable_i2c != raspi_i2c_enabled"
tags:
- raspi
- name: enable/disable serial
command: "raspi-config nonint do_serial {{ 0 if myconfig.enable_serial else 1 }}"
when: "'enable_serial' in myconfig and myconfig.enable_serial != raspi_serial_enabled"
tags:
- raspi
- name: enable/disable hardware serial
command: "raspi-config nonint do_serial_hw {{ 0 if myconfig.enable_serial_hw else 1 }}"
when: "'enable_serial_hw' in myconfig and myconfig.enable_serial_hw != raspi_serial_hw_enabled"
tags:
- raspi
- name: enable/disable onewire
command: "raspi-config nonint do_onewire {{ 0 if myconfig.enable_onewire else 1 }}"
when: "'enable_onewire' in myconfig and myconfig.enable_onewire != raspi_onewire_enabled"
tags:
- raspi
- name: enable/disable remote GPIO
command: "raspi-config nonint do_rgpio {{ 0 if myconfig.enable_rgpio else 1 }}"
when: "'enable_rgpio' in myconfig and myconfig.enable_rgpio != raspi_rgpio_enabled"
tags:
- raspi
- name: enable/disable HDMI overscan
command: "raspi-config nonint do_overscan {{ 0 if myconfig.enable_overscan else 1 }}"
when: "'enable_overscan' in myconfig and myconfig.enable_overscan != raspi_overscan_enabled"
tags:
- raspi
- name: expand file system
command: "raspi-config nonint do_expand_rootfs"
when: raspi_fs_expandable
tags:
- raspi

@ -0,0 +1,221 @@
---
- name: get Raspberry Pi model type
shell: "raspi-config nonint get_pi_type"
register: raspi_type
changed_when: False
check_mode: no
tags:
- raspi
- name: get hostname via raspi-config
shell: "raspi-config nonint get_hostname"
register: raspi_hostname
changed_when: False
check_mode: no
tags:
- raspi
- name: get boot-to-gui setting
shell: "raspi-config nonint get_boot_cli" # 0 == "boot to cli"; 1 == "boot to gui"
register: raspi_boot_gui
changed_when: False
check_mode: no
tags:
- raspi
- name: get autologin setting
shell: "raspi-config nonint get_autologin" # 0 == "enable autologin"; 1 == "disable autologin"
register: raspi_noautologin
changed_when: False
check_mode: no
tags:
- raspi
- name: get wait-for-network-on-boot setting
shell: "raspi-config nonint get_boot_wait" # 0 == "wait"; 1 == "don't wait"
register: raspi_boot_nowait
changed_when: False
check_mode: no
tags:
- raspi
- name: get splash screen setting
shell: "raspi-config nonint get_boot_splash" # 0 == "wait"; 1 == "don't wait"
register: raspi_boot_splash
changed_when: False
check_mode: no
tags:
- raspi
- name: get wifi country
shell: "raspi-config nonint get_wifi_country"
register: raspi_wifi_country
changed_when: False
check_mode: no
tags:
- raspi
- name: get camera status
shell: "raspi-config nonint get_camera" # 0 == "camera enabled"; 1 == "camera disabled"
register: raspi_camera_disabled
changed_when: False
check_mode: no
tags:
- raspi
- name: get ssh enabled status
shell: "raspi-config nonint get_ssh"
register: raspi_ssh_disabled
changed_when: False
check_mode: no
tags:
- raspi
- name: get VNC enabled status
shell: "raspi-config nonint get_vnc"
register: raspi_vnc_disabled
changed_when: False
check_mode: no
tags:
- raspi
- name: get SPI enabled status
shell: "raspi-config nonint get_spi"
register: raspi_spi_disabled
changed_when: False
check_mode: no
tags:
- raspi
- name: get I2C enabled status
shell: "raspi-config nonint get_i2c"
register: raspi_i2c_disabled
changed_when: False
check_mode: no
tags:
- raspi
- name: get serial enabled status
shell: "raspi-config nonint get_serial"
register: raspi_serial_disabled
changed_when: False
check_mode: no
tags:
- raspi
- name: get hardware serial enabled status
shell: "raspi-config nonint get_serial_hw"
register: raspi_serial_hw_disabled
changed_when: False
check_mode: no
tags:
- raspi
- name: get onewire enabled status
shell: "raspi-config nonint get_onewire"
register: raspi_onewire_disabled
changed_when: False
check_mode: no
tags:
- raspi
- name: get remote gpio enabled status
shell: "raspi-config nonint get_rgpio"
register: raspi_rgpio_disabled
changed_when: False
check_mode: no
tags:
- raspi
- name: get overclock state
shell: "raspi-config nonint get_config_var arm_freq /boot/config.txt"
register: raspi_overclock
changed_when: False
check_mode: no
tags:
- raspi
- name: get fs expandability
shell: "raspi-config nonint get_can_expand"
register: raspi_fs_unexpandable
changed_when: False
check_mode: no
tags:
- raspi
- name: get overscan setting
shell: "raspi-config nonint get_overscan"
register: raspi_overscan
changed_when: False
check_mode: no
tags:
- raspi
- name: get GPU memory split
shell: "raspi-config nonint get_config_var gpu_mem /boot/config.txt"
register: raspi_gpu_mem
changed_when: False
check_mode: no
tags:
- raspi
- name: get GPU memory split 256
shell: "raspi-config nonint get_config_var gpu_mem_256 /boot/config.txt"
register: raspi_gpu_mem_256
changed_when: False
check_mode: no
tags:
- raspi
- name: get GPU memory split 512
shell: "raspi-config nonint get_config_var gpu_mem_512 /boot/config.txt"
register: raspi_gpu_mem_512
changed_when: False
check_mode: no
tags:
- raspi
- name: get GPU memory split 1024
shell: "raspi-config nonint get_config_var gpu_mem_1024 /boot/config.txt"
register: raspi_gpu_mem_1024
changed_when: False
check_mode: no
tags:
- raspi
- name: determine last allocated disk sector
shell: "parted /dev/mmcblk0 -ms unit s p | tail -n1 | awk -F':' '{ print $3 + 0 }'"
register: raspi_last_alloced_sector
changed_when: False
check_mode: no
tags:
- raspi
- name: set raspi-config facts
set_fact:
raspi_type: "{{ raspi_type.stdout }}"
raspi_hostname: "{{ raspi_hostname.stdout }}"
raspi_wifi_country: "{{ raspi_wifi_country.stdout }}"
raspi_gui_enabled: "{{ raspi_boot_gui.stdout != '0' }}"
raspi_autologin_enabled: "{{ raspi_noautologin.stdout == '0' }}"
raspi_bootwait_enabled: "{{ raspi_boot_nowait.stdout == '0' }}"
raspi_bootsplash_enabled: "{{ raspi_boot_splash.stdout == '0' }}"
raspi_camera_enabled: "{{ raspi_camera_disabled.stdout == '0' }}"
raspi_ssh_enabled: "{{ raspi_ssh_disabled.stdout == '0' }}"
raspi_vnc_enabled: "{{ raspi_vnc_disabled.stdout == '0' and 'find' not in raspi_vnc_disabled.stderr and 'found' not in raspi_vnc_disabled.stderr }}"
raspi_spi_enabled: "{{ raspi_spi_disabled.stdout == '0' }}"
raspi_i2c_enabled: "{{ raspi_i2c_disabled.stdout == '0' }}"
raspi_serial_enabled: "{{ raspi_serial_disabled.stdout == '0' }}"
raspi_serial_hw_enabled: "{{ raspi_serial_hw_disabled.stdout == '0' }}"
raspi_onewire_enabled: "{{ raspi_onewire_disabled.stdout == '0' }}"
raspi_rgpio_enabled: "{{ raspi_rgpio_disabled.stdout == '0' }}"
raspi_overclock: "{{ raspi_overclock.stdout }}"
raspi_fs_expandable: "{{ raspi_fs_unexpandable.stdout == '0' and (raspi_last_alloced_sector.stdout|int + 1) < ansible_devices.mmcblk0.sectors|int }}"
raspi_overscan: "{{ raspi_overscan.stdout }}"
raspi_gpu_mem: "{{ raspi_gpu_mem.stdout }}"
raspi_gpu_mem_256: "{{ raspi_gpu_mem_256.stdout }}"
raspi_gpu_mem_512: "{{ raspi_gpu_mem_512.stdout }}"
raspi_gpu_mem_1024: "{{ raspi_gpu_mem_1024.stdout }}"
check_mode: no
tags:
- raspi

@ -0,0 +1,45 @@
---
# Uninstall Raspbian bloat
- name: remove raspbian bloat
apt:
name="{{ packages }}"
state=absent
vars:
packages:
- wolfram-engine
- libreoffice*
- scratch
- minecraft-pi
- python-minecraftpi
- python3-minecraftpi
- sonic-pi
- dillo
- gpiciew
- penguinspuzzle
tags:
- sw
# Install required software
- name: install basic software environment
apt:
name="{{ packages }}"
state=present
update_cache=yes
vars:
packages:
- vim
- git
- python-pip
- ufw
tags:
- sw
- name: install additional software
apt:
name="{{ myconfig.extra_software }}"
state=present
update_cache=yes
when: "'extra_software' in myconfig"
tags:
- sw

@ -0,0 +1,41 @@
---
### Switch to non-default user as soon as possible if possible
#- name: does primary login user exist?
# local_action: "command ssh -q -o ConnectTimeout=3 -l {{ create_users[0].name }} {{ inventory_hostname }} /bin/true"
# register: user_exists
# ignore_errors: true
# changed_when: false
#
#- name: switch remote_user if possible
# remote_user: "{{ user_exists | success | ternary(omit, create_users[0].name) }}"
# command: "/bin/true"
# changed_when: false
### Create user accounts
- name: create users
user: name="{{ item.name }}"
comment="{{ item.comment }}"
group="{{ item.group }}"
groups="{{ item.groups }}"
uid="{{ item.uid }}"
state=present
shell=/bin/bash
with_items: "{{ create_users }}"
tags: [ 'users' ]
- name: install ssh pubkeys for new users
authorized_key: user="{{ item.name }}"
key="{{ item.pubkey }}"
state=present
with_items: "{{ create_users }}"
tags: [ 'users' ]
### disable the 'pi' user's ability to login in with password
### if you enable this, you may lock yourself out--you must make sure another
### user has been added with both sudo privileges and a password by which
### sudo can be authenticated
#- name: disable 'pi' user
# user: name="pi"
# password="*"
# state=present
# tags: [ 'users' ]

@ -1,9 +1,46 @@
---
### Hosts that must be present in /etc/hosts
etc_hosts_contents:
- { name: 'clovermill', ip: '192.168.1.149' }
- { name: 'cloverfield', ip: '192.168.1.153' }
- { name: 'clovermine', ip: '192.168.1.154' }
macaddrs:
b8:27:eb:39:d7:57:
hostname: "clovermine"
domain: "local"
locale: "en_US.UTF-8"
timezone: "America/Los_Angeles"
xkblayout: "us"
wifi_country: "US"
enable_gui: True
enable_autologin: False
enable_bootwait: False
enable_bootsplash: False
enable_camera: False
enable_vnc: False
enable_spi: False
enable_i2c: False
enable_serial: True
enable_serial_hw: True
enable_onewire: False
enable_rgpio: False
b8:27:eb:ff:35:c7:
hostname: "cloverleaf"
domain: "local"
locale: "en_US.UTF-8"
timezone: "America/Los_Angeles"
xkblayout: "us"
enable_gui: False
enable_autologin: False
enable_bootwait: True
enable_bootsplash: False
enable_camera: False
enable_vnc: False
enable_spi: False
enable_i2c: False
enable_serial: True
enable_serial_hw: True
enable_onewire: False
enable_rgpio: False
extra_software:
- "w3m"
- "irssi"
- "screen"
### Users that must be present on the system
create_users:

@ -1,4 +0,0 @@
---
- hosts: all
roles:
- role: common
Loading…
Cancel
Save