├── .github ├── FUNDING.yml └── workflows │ └── stale.yml ├── .gitignore ├── LICENSE ├── README.md ├── ansible.cfg ├── default.config.yml ├── example.inventory ├── main.yml ├── requirements.yml ├── reset.yml ├── roles ├── appearance │ ├── README.md │ └── tasks │ │ └── main.yml ├── cawbird │ ├── README.md │ ├── handlers │ │ └── main.yml │ └── tasks │ │ └── main.yml ├── dropbox │ ├── README.md │ └── tasks │ │ └── main.yml ├── evolution │ ├── README.md │ └── tasks │ │ └── main.yml ├── github_cli │ ├── README.md │ └── tasks │ │ └── main.yml ├── input_devices │ ├── README.md │ ├── handlers │ │ └── main.yml │ └── tasks │ │ └── main.yml ├── mirage │ ├── README.md │ └── tasks │ │ └── main.yml ├── pidgin │ ├── README.md │ └── tasks │ │ └── main.yml ├── rclone │ ├── README.md │ ├── handlers │ │ └── main.yml │ └── tasks │ │ └── main.yml ├── sound_recorder │ ├── README.md │ └── tasks │ │ └── main.yml └── vscodium │ ├── README.md │ └── tasks │ └── main.yml └── tasks └── init.yml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | --- 3 | github: geerlingguy 4 | patreon: geerlingguy 5 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Close inactive issues 3 | 'on': 4 | schedule: 5 | - cron: "55 14 * * 4" # semi-random time 6 | 7 | jobs: 8 | close-issues: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | issues: write 12 | pull-requests: write 13 | steps: 14 | - uses: actions/stale@v8 15 | with: 16 | days-before-stale: 120 17 | days-before-close: 60 18 | exempt-issue-labels: bug,pinned,security,planned 19 | exempt-pr-labels: bug,pinned,security,planned 20 | stale-issue-label: "stale" 21 | stale-pr-label: "stale" 22 | stale-issue-message: | 23 | This issue has been marked 'stale' due to lack of recent activity. If there is no further activity, the issue will be closed in another 30 days. Thank you for your contribution! 24 | 25 | Please read [this blog post](https://www.jeffgeerling.com/blog/2020/enabling-stale-issue-bot-on-my-github-repositories) to see the reasons why I mark issues as stale. 26 | close-issue-message: | 27 | This issue has been closed due to inactivity. If you feel this is in error, please reopen the issue or file a new issue with the relevant details. 28 | stale-pr-message: | 29 | This pr has been marked 'stale' due to lack of recent activity. If there is no further activity, the issue will be closed in another 30 days. Thank you for your contribution! 30 | 31 | Please read [this blog post](https://www.jeffgeerling.com/blog/2020/enabling-stale-issue-bot-on-my-github-repositories) to see the reasons why I mark issues as stale. 32 | close-pr-message: | 33 | This pr has been closed due to inactivity. If you feel this is in error, please reopen the issue or file a new issue with the relevant details. 34 | repo-token: ${{ secrets.GITHUB_TOKEN }} 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | config.yml 2 | inventory 3 | roles/geerlingguy.* 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jeff Geerling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Raspberry Pi Development Ansible Playbook 2 | 3 | This playbook installs and configures most of the software I use on my Mac for web and software development. Some things in macOS are slightly difficult to automate, so I still have some manual installation steps, but at least it's all documented here. 4 | 5 | This is a work in progress, and is mostly a means for me to document my current Mac's setup. I'll be evolving this set of playbooks over time. 6 | 7 | _See also_: 8 | 9 | - [Mac Development Ansible Playbook](https://github.com/geerlingguy/mac-dev-playbook) 10 | 11 | ## Installation 12 | 13 | 1. [Install Ansible](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html). 14 | 2. Clone this repository to your local drive. 15 | 3. Run `$ ansible-galaxy install -r requirements.yml` inside this directory to install required Ansible roles. 16 | 4. Run `ansible-playbook main.yml -c local` inside this directory. 17 | 18 | Note: If you run the playbook from a different computer via SSH, remove the `-c local` from the command above. 19 | 20 | ## Overriding Defaults 21 | 22 | Not everyone's development environment and preferred software configuration is the same. 23 | 24 | You can override any of the defaults configured in `default.config.yml` by creating a `config.yml` file and setting the overrides in that file. 25 | 26 | For example, TODO. 27 | 28 | ## Included Applications / Configuration (Default) 29 | 30 | TODO. 31 | 32 | ## Ansible for DevOps 33 | 34 | Check out [Ansible for DevOps](https://www.ansiblefordevops.com), which teaches you how to automate almost anything with Ansible. 35 | 36 | ## Author 37 | 38 | [Jeff Geerling](https://www.jeffgeerling.com), 2020. 39 | -------------------------------------------------------------------------------- /ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | roles_path = ./roles 3 | nocows = 1 4 | retry_files_enabled = False 5 | stdout_callback = yaml 6 | # bin_ansible_callbacks = True 7 | inventory = inventory 8 | 9 | [ssh_connection] 10 | pipelining = True 11 | control_path = /tmp/ansible-ssh-%%h-%%p-%%r 12 | -------------------------------------------------------------------------------- /default.config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Security configuration. 3 | security_sudoers_passwordless: ['pi'] 4 | firewall_allowed_tcp_ports: 5 | - 22 6 | - 80 7 | - 443 8 | 9 | # Python configuration. 10 | pip_package: python3-pip 11 | pip_executable: pip3 12 | docker_pip_executable: '{{ pip_executable }}' 13 | 14 | # Nginx load balancing configuration (disabled by default). 15 | nginx_use_as_lb: false 16 | nginx_lb_backends: 17 | - '10.0.100.62' 18 | - '10.0.100.63' 19 | - '10.0.100.64' 20 | -------------------------------------------------------------------------------- /example.inventory: -------------------------------------------------------------------------------- 1 | [pi] 2 | 127.0.0.1 ansible_python_interpreter=/usr/bin/python3 3 | 4 | # Comment the default localhost line above, and uncomment the line below 5 | # (replacing the IP with your Pi's IP address) if running the playbook from a 6 | # separate workstation. 7 | #10.0.100.20 ansible_user=pi ansible_python_interpreter=/usr/bin/python3 8 | -------------------------------------------------------------------------------- /main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: pi 3 | become: yes 4 | 5 | vars_files: 6 | - default.config.yml 7 | 8 | pre_tasks: 9 | - name: Include config override file, if it exists. 10 | include_vars: "{{ item }}" 11 | with_fileglob: 12 | - config.yml 13 | tags: ['always'] 14 | 15 | - import_tasks: tasks/init.yml 16 | 17 | roles: 18 | - role: geerlingguy.security 19 | tags: ['security', 'ssh'] 20 | 21 | - role: geerlingguy.firewall 22 | tags: ['security', 'firewall'] 23 | 24 | - role: geerlingguy.git 25 | tags: ['git'] 26 | 27 | - role: geerlingguy.pip 28 | tags: ['pip'] 29 | 30 | - role: geerlingguy.docker_arm 31 | tags: ['docker'] 32 | 33 | - role: appearance 34 | tags: ['appearance'] 35 | 36 | - role: input_devices 37 | tags: ['input'] 38 | 39 | - role: vscodium 40 | tags: ['apps', 'vscodium'] 41 | 42 | - role: github_cli 43 | tags: ['apps', 'gh'] 44 | 45 | - role: evolution 46 | tags: ['apps', 'evolution'] 47 | 48 | - role: rclone 49 | tags: ['apps', 'rclone'] 50 | 51 | # - role: dropbox 52 | # tags: ['apps', 'dropbox'] 53 | 54 | - role: cawbird 55 | tags: ['apps', 'cawbird'] 56 | 57 | - role: mirage 58 | tags: ['apps', 'mirage'] 59 | 60 | - role: pidgin 61 | tags: ['apps', 'pidgin'] 62 | 63 | - role: sound_recorder 64 | tags: ['apps', 'sound_recorder'] 65 | 66 | tasks: [] 67 | -------------------------------------------------------------------------------- /requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - src: geerlingguy.security 3 | - src: geerlingguy.firewall 4 | - src: geerlingguy.git 5 | - src: geerlingguy.pip 6 | - src: geerlingguy.docker_arm 7 | -------------------------------------------------------------------------------- /reset.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: pi 3 | become: yes 4 | 5 | vars_files: 6 | - default.config.yml 7 | 8 | tasks: 9 | - name: Include config override file, if it exists. 10 | include_vars: "{{ item }}" 11 | with_fileglob: 12 | - config.yml 13 | 14 | - name: Completely destroy docker-compose environment. 15 | command: > 16 | docker-compose down -v 17 | chdir={{ drupal_pi_app_directory }} 18 | 19 | - name: Delete the contents of the host Drupal files directory. 20 | shell: > 21 | rm -rf {{ drupal_files_directory_host }}/* 22 | warn=false 23 | changed_when: true 24 | 25 | # Can't use docker_service module until the following issue is resolved: 26 | # https://github.com/ansible/ansible/issues/26937 27 | - name: Rebuild the docker-compose environment. 28 | command: > 29 | docker-compose up -d --remove-orphans 30 | chdir={{ drupal_pi_app_directory }} 31 | -------------------------------------------------------------------------------- /roles/appearance/README.md: -------------------------------------------------------------------------------- 1 | # Appearance Settings for Raspberry Pi 2 | 3 | This role modifies some appearance settings on the Raspberry Pi to my liking. 4 | 5 | ## Changing the GTK 3.0 Theme 6 | 7 | This role attempts to switch the GTK 3.0 theme, but it seems to not be able to apply the change on the fly. 8 | 9 | So, to change the theme manually, run `lxappearance` and then choose 'Adwaita-dark'. Sigh. -------------------------------------------------------------------------------- /roles/appearance/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Set the Adwaita-dark theme for GTK. 3 | lineinfile: 4 | path: /home/pi/.config/gtk-3.0/settings.ini 5 | regexp: '^gtk-theme-name=' 6 | line: 'gtk-theme-name=Adwaita-dark' 7 | register: gtk_theme_switch 8 | 9 | # TODO: This does nothing right now. Not sure how to change from CLI. 10 | - name: Reload GTK theme if switched. 11 | shell: | 12 | theme=$(gsettings get org.gnome.desktop.interface gtk-theme) 13 | gsettings set org.gnome.desktop.interface gtk-theme '' 14 | sleep 1 15 | gsettings set org.gnome.desktop.interface gtk-theme $theme 16 | become: false 17 | when: gtk_theme_switch is changed 18 | -------------------------------------------------------------------------------- /roles/cawbird/README.md: -------------------------------------------------------------------------------- 1 | # Cawbird installation for Raspberry Pi 2 | 3 | This role installs [Cawbird](https://snapcraft.io/install/cawbird/raspbian). 4 | 5 | For some inexplicable reason, it's only available as a 'snap', which means you have to install `snapd` before you can install `cawbird` via `snap`. I hate Snaps. 6 | 7 | Also, if you want Cawbird to be available in your Raspberry Pi application menu, you have to go to Pi Menu > Preferences > Main menu editor. Then add a 'New Item' where you want it, with 'Command' set to `cawbird`. 8 | -------------------------------------------------------------------------------- /roles/cawbird/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: cawbird_reboot_required 3 | debug: 4 | msg: To complete installation of Cawbird, please reboot and run the playbook again. 5 | -------------------------------------------------------------------------------- /roles/cawbird/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure snapd is installed. 3 | apt: 4 | name: snapd 5 | state: present 6 | register: snapd_install 7 | notify: cawbird_reboot_required 8 | 9 | - name: Ensure cawbird is installed (via snap). 10 | snap: 11 | name: cawbird 12 | state: present 13 | when: snapd_install is not changed 14 | -------------------------------------------------------------------------------- /roles/dropbox/README.md: -------------------------------------------------------------------------------- 1 | # Dropbox installation for Linux 2 | 3 | This role installs Dropbox on Linux. 4 | -------------------------------------------------------------------------------- /roles/dropbox/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install prerequisites. 3 | apt: 4 | name: 5 | - libnautilus-extension-dev 6 | - python-docutils 7 | state: present 8 | 9 | # The installation of the libnautilus-extension-dev package 10 | # failed initially; see GitHub issue: 11 | # https://github.com/raspberrypi/Raspberry-Pi-OS-64bit/issues/5 12 | # 13 | # Solution was found on: 14 | # https://answers.ros.org/question/218263/how-to-resolve-dependency-issues-when-installing-multimaster-fkie-on-ubuntu-arm/?answer=256145#post-id-256145 15 | # 16 | # sudo apt-get download libegl-dev libgles-dev 17 | # sudo dpkg -i --force-overwrite /var/cache/apt/archives/libgl-dev_1.3.0-7~bpo10+1_arm64.deb 18 | # sudo dpkg -i --force-overwrite /var/cache/apt/archives/libegl-dev_1.3.0-7~bpo10+1_arm64.deb 19 | # sudo apt-get install -f 20 | 21 | # See: https://linux.dropbox.com/packages/ 22 | - name: Extract Dropbox installer. 23 | unarchive: 24 | src: https://linux.dropbox.com/packages/nautilus-dropbox-1.6.2.tar.bz2 25 | dest: /opt 26 | remote_src: true 27 | 28 | - name: Run configure on Dropbox. 29 | command: > 30 | ./configure --build=unknown-unknown-linux 31 | chdir=/opt/nautilus-dropbox-1.6.2 32 | creates=/tmp/somefiletodo 33 | 34 | - name: Make Dropbox. 35 | make: 36 | chdir: /opt/nautilus-dropbox-1.6.2 37 | 38 | - name: Install Dropbox. 39 | make: 40 | chdir: /opt/nautilus-dropbox-1.6.2 41 | target: install 42 | register: dropbox_make 43 | 44 | - name: Start Dropbox and install the daemon. 45 | command: dropbox start -i 46 | when: dropbox_make is changed 47 | -------------------------------------------------------------------------------- /roles/evolution/README.md: -------------------------------------------------------------------------------- 1 | # Evolution install for Rasbperry Pi 2 | 3 | This role installs Evolution, an email client for Linux. 4 | -------------------------------------------------------------------------------- /roles/evolution/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install Evolution email client. 3 | apt: 4 | name: evolution 5 | state: present 6 | -------------------------------------------------------------------------------- /roles/github_cli/README.md: -------------------------------------------------------------------------------- 1 | # GitHub CLI installation 2 | 3 | This role installs the [GitHub CLI](https://github.com/cli/cli) on Raspberry Pi OS. 4 | -------------------------------------------------------------------------------- /roles/github_cli/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install the GitHub CLI from .deb package. 3 | apt: 4 | deb: https://github.com/cli/cli/releases/download/v0.9.0/gh_0.9.0_linux_arm64.deb 5 | -------------------------------------------------------------------------------- /roles/input_devices/README.md: -------------------------------------------------------------------------------- 1 | # Raspberry Pi Input Devices Setup 2 | 3 | Setup and configuration for input devices for the Raspberry Pi. 4 | -------------------------------------------------------------------------------- /roles/input_devices/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: needs_reboot 3 | debug: 4 | msg: Some settings changes that were applied require a reboot. 5 | -------------------------------------------------------------------------------- /roles/input_devices/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Enable natural scrolling direction. 3 | blockinfile: 4 | path: /usr/share/X11/xorg.conf.d/40-libinput.conf 5 | block: "{{ item }}" 6 | notify: needs_reboot 7 | with_items: 8 | - | 9 | Section "InputClass" 10 | Identifier "libinput pointer catchall" 11 | MatchIsPointer "on" 12 | MatchDevicePath "/dev/input/event*" 13 | Driver "libinput" 14 | Option "NaturalScrolling" "true" 15 | EndSection 16 | - | 17 | Section "InputClass" 18 | Identifier "libinput touchpad catchall" 19 | MatchIsTouchpad "on" 20 | MatchDevicePath "/dev/input/event*" 21 | Driver "libinput" 22 | Option "NaturalScrolling" "true" 23 | EndSection 24 | -------------------------------------------------------------------------------- /roles/mirage/README.md: -------------------------------------------------------------------------------- 1 | # Mirage installation 2 | 3 | This role installs Mirage, a very simple image editor and viewer for Linux. 4 | -------------------------------------------------------------------------------- /roles/mirage/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install mirage image editor. 3 | apt: 4 | name: mirage 5 | state: present 6 | -------------------------------------------------------------------------------- /roles/pidgin/README.md: -------------------------------------------------------------------------------- 1 | # Pidgin installation 2 | 3 | This role installs Pidgin, a client for chat. 4 | -------------------------------------------------------------------------------- /roles/pidgin/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install Pidgin. 3 | apt: 4 | name: pidgin 5 | state: present 6 | -------------------------------------------------------------------------------- /roles/rclone/README.md: -------------------------------------------------------------------------------- 1 | # Rclone installation for Linux. 2 | 3 | This role installs `rclone` for Linux. I use it to mount my Dropbox in the path `/home/pi/Dropbox`. 4 | 5 | After installation, you have to configure your Dropbox and run the daemon to manage the mount: 6 | 7 | ``` 8 | $ rclone config 9 | $ rclone mount --daemon dropbox: /home/pi/Dropbox 10 | ``` 11 | -------------------------------------------------------------------------------- /roles/rclone/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: rclone_setup_required 3 | debug: 4 | msg: | 5 | Rclone was installed successfully. To set up Dropbox, run: 6 | $ rclone config 7 | $ rclone mount --daemon dropbox: /home/pi/Dropbox 8 | -------------------------------------------------------------------------------- /roles/rclone/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install rclone. 3 | apt: 4 | name: rclone 5 | state: present 6 | notify: rclone_setup_required 7 | 8 | - name: Ensure Dropbox folder exists. 9 | file: 10 | path: /home/pi/Dropbox 11 | state: directory 12 | owner: pi 13 | group: pi 14 | failed_when: false 15 | -------------------------------------------------------------------------------- /roles/sound_recorder/README.md: -------------------------------------------------------------------------------- 1 | # GNOME sound recorder 2 | 3 | This role installs GNOME sound recorder, for recording audio. 4 | -------------------------------------------------------------------------------- /roles/sound_recorder/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install gnome-sound-recorder. 3 | package: 4 | name: gnome-sound-recorder 5 | state: present 6 | -------------------------------------------------------------------------------- /roles/vscodium/README.md: -------------------------------------------------------------------------------- 1 | # VSCodium for Raspberry Pi 2 | 3 | This role installs VSCodium (the open source telemetry-free version of VSCode) on the Raspberry Pi. 4 | -------------------------------------------------------------------------------- /roles/vscodium/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Ensure dependencies are present. 3 | apt: 4 | name: 5 | - apt-transport-https 6 | - gnupg2 7 | state: present 8 | 9 | - name: Add paulcarroty's apt key. 10 | apt_key: 11 | url: https://gitlab.com/paulcarroty/vscodium-deb-rpm-repo/raw/master/pub.gpg 12 | id: "5A278D9C" 13 | state: present 14 | 15 | - name: Add paulcarroty's vscodium repository for vscodium. 16 | apt_repository: 17 | repo: "deb https://gitlab.com/paulcarroty/vscodium-deb-rpm-repo/raw/repos/debs/ vscodium main" 18 | state: present 19 | register: vscodium_repo 20 | 21 | - name: Update apt cache if repo was added. 22 | apt: 23 | update_cache: true 24 | when: vscodium_repo.changed 25 | tags: ['skip_ansible_lint'] 26 | 27 | - name: Ensure VSCodium is installed. 28 | apt: 29 | name: codium 30 | state: present 31 | -------------------------------------------------------------------------------- /tasks/init.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Update apt cache if needed. 3 | apt: 4 | update_cache: true 5 | cache_valid_time: 86400 6 | 7 | - name: Ensure /usr/local/bin exists. 8 | file: 9 | path: /usr/local/bin 10 | state: directory 11 | mode: 0775 12 | 13 | - name: Ensure auth.log file is present. 14 | copy: 15 | dest: /var/log/auth.log 16 | content: "" 17 | force: false 18 | 19 | - name: Ensure dependencies are installed. 20 | apt: 21 | name: 22 | - curl 23 | - openssh-server 24 | - openssh-client 25 | - libffi-dev 26 | - libssl-dev 27 | - python-backports.ssl-match-hostname 28 | - cron 29 | state: present 30 | --------------------------------------------------------------------------------