How I Automated Firefox Install Via Ansible
The Problem
Internet browsers are a hotly debated topic with nerds on the internet. I’m not here today to sway your opinion, I just wanted to showcase how I used Ansible to help solve a problem that using Firefox on Debian causes.
As you may, or may not know, the default repositories on Debian do not include an up to date firefox. At the time of writing, this version mismatch is not that big. The repos have version 115.3.0, while the current version is 118.0.2. Over time this mismatch becomes huge. I don’t mind “too much” that most of Debian’s software is outdated. But when it comes to my web browser? I don’t want to be behind date at all.
Possible Solutions…
Now there’s a few ways to handle this.
- Use the snap
- Use the flatpack
- Use an alternative package manager, such as guix or nix.
- Download & unpack the tar binary
I’m not going to lie, options 1 & 2 are not ideal for me. I do have “some” flatpacks installed, but even those are a compromise. Also I’m currently moving away from Nix for personal reasons I may get into in a future post. I still think Nix is the coolest operating system & way to handle a system - but it’s not mainstream.
For this, I wanted to find a mainstream solution that I could use in the real world. So that meant the solution I devised had to have a few ground rules.
- Partially distro agnostic
- I should be able to do this method of install on any distro
- It should use a tool that’s in use in the real world
- It should configure as much of the install as possible without user intervention
This meant one thing for me: ansible. Ansible lets you automate any process easily, and I’ve been dying to use it more to make my understanding of it in a professional setting… well… better!
The Solution
In my attempt to move away from Nix, but hold the principles of system configuration via files true, i opted to do a lot of work setting up my most recent debian install with ansible. The following is just a small sliver of the setup I made, focusing on firefox.
The Project Directory Layout
celer@stinkpad ~/git/debian-ansible-laptop $ tree .
.
├── firefox.yml
├── inventories
│ └── hosts.yml
├── README.md
├── roles
│ ├── firefox
│ │ ├── files
│ │ │ └── firefox.desktop
│ │ └── tasks
│ │ └── main.yml
Before I go into the specifics of commands we should review how the files are laid out here. I think this is important for anyone unfamiliar with Ansible to see to understand what the commands do, and what files they reference.
Inventories
[local-machine]
127.0.0.1 ansible_user=celer ansible_port=22
Firefox.yml
- hosts: local-machine
become: true
roles:
- firefox
Firefox/tasks/main.yml
- name: Ensure pre reqs exist
apt:
name:
- wget
- tar
- curl
- libdbus-glib-1-2 #Another requirement for firefox to even start
- jq # Needed for getting the latest FF version online
- name: Get latest firefox version number from the net
shell:
cmd: curl -s https://product-details.mozilla.org/1.0/firefox_versions.json | jq .LATEST_FIREFOX_VERSION | sed 's/"//g'
register: online_ff_version
- name: See if firefox is installed
stat:
path: "/opt/firefox/firefox"
register: does_ff_exist
- name: See installed version of firefox version
shell:
cmd: "/opt/firefox/firefox --version | sed 's/Mozilla Firefox //g'"
register: installed_ff_version
when: does_ff_exist.stat.exists
- name: Define target firefox version
set_fact:
firefox_version: "{{ online_ff_version.stdout }}"
- name: Ensure folder exists
file:
path: "/opt/firefox"
state: "directory"
when: not does_ff_exist.stat.exists
- name: Download firefox
shell:
cmd: "wget -O /opt/firefox.tar.bz2 https://download-installer.cdn.mozilla.net/pub/firefox/releases/{{ firefox_version }}/linux-x86_64/en-US/firefox-{{ firefox_version }}.tar.bz2"
become: yes
when: "firefox_version != installed_ff_version.stdout"
- name: Unpack firefox
unarchive:
src: /opt/firefox.tar.bz2
dest: /opt
remote_src: yes
when: "firefox_version != installed_ff_version.stdout"
- name: Copy firefox desktop entry to proper location
copy:
src: ../files/firefox.desktop
dest: /usr/share/applications/firefox.desktop
when: "firefox_version != installed_ff_version.stdout"
- name: Remove unwanted files
file:
path: /opt/firefox.tar.bz2
state: absent
when: "firefox_version != installed_ff_version.stdout"
firefox.desktop
[Desktop Entry]
Type=Application
Name=Firefox
Comment=The Free And Open Internet Browser
Path=/opt/firefox
Exec=env MOZ_ENABLE_WAYLAND=1 /opt/firefox/firefox-bin
Icon=/opt/firefox/browser/chrome/icons/default/default48.png
Terminal=false
Categories=Internet
Files Explained
When we’re in the project’s root directory, we invoke the playlist that orchestrates this all together like so:
ansible-playbook -i inventories/hosts.yml firefox.yml main.yml -K
Let’s break this down.
ansible-playbook
is the main command used to invoke an ansible playbook!main.yml
is the said playbook- Inside of
main.yml
we specify where to run the install (local-machine) - Inside the
inventory
we define what local-machine is (localhost naturally) - The
main.yml
says to run the main task in thefirefox
role.- This is the
roles/firefox/tasks/main.yml
file - This is also where the bulk of the work is done
- This is the
- The firefox role at one point copies the
firefox.desktop
to the target destination- This file is needed to start Firefox from typical “start menus” 1
Where the magic happens - the firefox role
The main reason I wanted to make this blogpost was to explain how I solved the problem of only installing firefox if
- The program is missing
- There is a newer version of firefox
This is both idempotent and more light on resources - I shouldn’t run downloads unless I have to.
First we install pre requirements
- name: Ensure pre reqs exist
apt:
name:
- wget
- tar
- curl
- libdbus-glib-1-2 #Another requirement for firefox to even start
- jq # Needed for getting the latest FF version online
These are programs needed for both Firefox to start, and for the script to run properly.
- name: Get latest firefox version number from the net
shell:
cmd: curl -s https://product-details.mozilla.org/1.0/firefox_versions.json | jq .LATEST_FIREFOX_VERSION | sed 's/"//g'
register: online_ff_version
Here, we use curl
in combination with jq
and sed
to get the current newest version of firefox. This would print out like 118.0.2
. Note the sed statement, that’s because by default it prints like this "118.0.2"
which breaks a comparison made with this number later.
- name: See if firefox is installed
stat:
path: "/opt/firefox/firefox"
register: does_ff_exist
- name: See installed version of firefox version
shell:
cmd: "/opt/firefox/firefox --version | sed 's/Mozilla Firefox //g'"
register: installed_ff_version
when: does_ff_exist.stat.exists
Here we check if firefox already exists on the system. If it does, we also check what our currently installed version is.
- name: Define target firefox version
set_fact:
firefox_version: "{{ online_ff_version.stdout }}"
- name: Ensure folder exists
file:
path: "/opt/firefox"
state: "directory"
when: not does_ff_exist.stat.exists
The first step here just saves the longer variable name (online_ff_version.stdout) to firefox_version
for later use. Then we make sure the host folder exists for firefox to be unpacked to if firefox doesn’t already exist
- name: Download firefox
shell:
cmd: "wget -O /opt/firefox.tar.bz2 https://download-installer.cdn.mozilla.net/pub/firefox/releases/{{ firefox_version }}/linux-x86_64/en-US/firefox-{{ firefox_version }}.tar.bz2"
become: yes
when: "firefox_version != installed_ff_version.stdout"
- name: Unpack firefox
unarchive:
src: /opt/firefox.tar.bz2
dest: /opt
remote_src: yes
when: "firefox_version != installed_ff_version.stdout"
Now we get to the work of actually downloading & unpacking the firefox binary - if the version available online is newer
- name: Copy firefox desktop entry to proper location
copy:
src: ../files/firefox.desktop
dest: /usr/share/applications/firefox.desktop
when: "firefox_version != installed_ff_version.stdout"
- name: Remove unwanted files
file:
path: /opt/firefox.tar.bz2
state: absent
when: "firefox_version != installed_ff_version.stdout"
Finally we copy over the firefox desktop entry and remove the .tar file left over.
Conclusion
The script above could be optimized down more (I could clean up variable names and standardize how I refer to values that variables hold), but for now this is just fine for me. Hopefully this helps out someone else trying to wrap their mind around how to use ansible properly!
Footnotes
-
Better known as dmenus, desktop menus, desktop menu entries, etc. ↩︎