Ansible: Modify cmdline.txt on Raspberry Pi

Viewed 1668

I am modifying /boot/cmdline.txt to add container features to a Raspberry Pi, so I need to add cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory into the file, within the same line.

I am trying to do it with the lineinfile module without much success:

- hosts: mypi
  become: yes
  tasks:
  - name: Enable container features
    lineinfile:
      path: /boot/cmdline.txt
      regex: " cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory"
      line: " cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory"
      insertafter: EOF
      state: present

I have been trying modifying the insertafter to BOF, using insertbefore too, using a regex to match the last word... But it ends up adding a carriage return. I have been unable to find some way to not add a new line.

4 Answers

Since you only have the one line in the file, you can do that with either replace or lineinfile. Here is the replace version:

  - name: Enable container features
    replace:
      path: cmdline.txt
      regexp: '^([\w](?!.*\b{{ item }}\b).*)$'
      replace: '\1 {{ item }}'
    with_items:
    - "cgroup_enable=cpuset"
    - "cgroup_memory=1"
    - "cgroup_enable=memory"

Stole the answer from here

As Vladimir pointed out, Jack's answer unfortunately is not sufficient for empty files and also fails if the desired argument already exists at the beginning of the line. The following suggested solution should address those issues. In particular, it is supposed to

  • support empty files,
  • support existing arguments at any position within the string,
  • be robust even with multi-line files (just in case...),
  • be idempotent, and
  • optionally update existing keys with the desired value.
# cmdline.yml

- name: read cmdline.txt
  become: true
  slurp: "src={{ cmdline_txt_path }}"
  register: result_cmdline

- name: generate regular expression for existing arguments
  set_fact:
    regex_existing: '{{ "\b" + key|string + "=" + ("[\w]*" if update else value|string) + "\b" }}'
    key_value_pair: '{{ key|string + "=" + value|string }}'

- name: generate regular expression for new arguments
  set_fact:
    regex_add_missing: '{{ "^((?!(?:.|\n)*" + regex_existing + ")((?:.|\n)*))$" }}'

- name: update cmdline.txt
  become: true
  copy:
    content: '{{ result_cmdline.content
        | b64decode
        | regex_replace(regex_existing, key_value_pair)
        | regex_replace(regex_add_missing, key_value_pair + " \1")
      }}'
    dest: "{{ cmdline_txt_path }}"

Usage:

- set_fact:
    cmdline_txt_path: /boot/cmdline.txt

- include_tasks: cmdline.yml
  vars:
    key: cgroup_enable
    value: memory
    update: false
    # will add the argument if the key-value-pair doesn't exist

- include_tasks: cmdline.yml
  vars:
    key: cgroup_enable
    value: cpu
    update: false

- include_tasks: cmdline.yml
  vars:
    key: cgroup_memory
    value: 1
    update: true
    # will replace the value of the first matching key, if found;
    # will add it if it's not found

However, I might have missed some edge cases - please let me know if you find any issues.

Q: "Ansible lineinfile module: Do not add new line. Find some way to not add a new line."

A: It's not possible. New line will be always added by module lineinfile. See source for example

b_lines.insert(index[1], b_line + b_linesep)

This is how a new line is added. Such additions will be terminated with b_linesep. See how the variable is defined

b_linesep = to_bytes(os.linesep, errors='surrogate_or_strict')

The os.linesep is used when you want to iterate through the lines of a text file. The internal scanner recognizes the os.linesep and replaces it with a single "\n".

See What is os.linesep for?.


The task with the module replace doesn't solve this problem either. Neither it creates the line without a newline, nor it modifies existing one this way. In addition to this it's not idempotent.

  - name: Enable container features
    replace:
      path: cmdline.txt
      regexp: '^([\w](?!.*\b{{ item }}\b).*)$'
      replace: '\1 {{ item }}'
    loop:
      - "cgroup_enable=cpuset"
      - "cgroup_memory=1"
      - "cgroup_enable=memory"

It will do nothing if the file is empty

TASK [Enable container features]
ok: [localhost] => (item=cgroup_enable=cpuset)
ok: [localhost] => (item=cgroup_memory=1)
ok: [localhost] => (item=cgroup_enable=memory)

If the line is present in the file this task will change it

shell> cat cmdline.txt 
cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory
ASK [Enable container features] *****************************************
ok: [localhost] => (item=cgroup_enable=cpuset)
--- before: cmdline.txt
+++ after: cmdline.txt
@@ -1 +1 @@
-cgroup_memory=1 cgroup_enable=memory cgroup_enable=cpuset
+cgroup_memory=1 cgroup_enable=memory cgroup_enable=cpuset cgroup_memory=1

I followed all of the different strategies laid out above, but in the end, I wanted something simple, as this is my first playbook, and I need to understand it now, and when I pick it up again later,.

My cmdline.txt contained multiple lines:

cat /boot/cmdline.txt -E 
console=serial0,115200 console=tty1 rootfstype=ext4 fsck.repair=yes rootwait cgroup_enable=memory cgroup_enable=cpuset cgroup_memory=1$
dtoverlay=pi3-disable-bt$ 
dtoverlay=pi3-disable-wifi$

So, the approach I was looking for:

  • Would ignore the other configurations in the cmdline.txt
  • Would only add the add a specific key=value if it was missing
  • It had to be idempotent

I settled on a simple regex to decide if this was the row I wanted to edit:

  • If the row contaied console= (as this is the row I'm after)
  • AND.. If the row does not contain cgroup_memory=1

- name: Adding cgroup_enable=memory to boot parameters for k3s
  lineinfile:
    path: /boot/cmdline.txt
    state: present
    regexp: '^((?!.*cgroup_enable=memory).*console.*)$'
    line: '\1 cgroup_enable=memory'
    backrefs: yes
  notify: reboot

- name: Adding cgroup_enable=cpuset to boot parameters for K3s
  lineinfile:
    path: /boot/cmdline.txt
    state: present
    regexp: '^((?!.*cgroup_enable=cpuset).*console.*)$'
    line: '\1 cgroup_enable=cpuset'
    backrefs: yes
  notify: reboot

- name: Adding cgroup_memory=1 to boot parameters for K3s
  lineinfile:
    path: /boot/cmdline.txt
    state: present
    regexp: '^((?!.*cgroup_memory=1).*console.*)$'
    line: '\1 cgroup_memory=1'
    backrefs: yes
  notify: reboot

And at some point in future, I'll probably condense all three of these into a single loop task. But not today.

Related