Giter Site home page Giter Site logo

ansible-openwrt's Introduction

Ansible Role: openwrt

Manage OpenWRT and derivatives with Ansible but without Python.

By putting a host in the inventory group openwrt, some modules are replaced with a shell version running on a standard OpenWRT installation, trying to preserve most of the original functionality. Hosts, that are not in this group are not affected. This makes it possible to have tasks mixed with OpenWRT and other platforms. There are also some new, OpenWRT specific modules included (like uci). Not all argument combinations are tested! Some cases have only been translated from Python for completeness' sake.

Currently, the following modules have been implemented:

  • command
  • copy
  • fetch (implicit)
  • file
  • lineinfile
  • nohup (new)
  • opkg
  • ping
  • service
  • setup
  • shell (implicit)
  • slurp
  • stat
  • sysctl
  • template (implicit)
  • uci (new)
  • wait_for_connection (implicit)

To achieve all this, some monkey patching is involved (in case you wonder about the vars_plugins).

Compatibility

This role was tested successfully with:

  • LEDE 17.01 (manually)
  • OpenWRT 18.06
  • OpenWRT 19.07
  • OpenWRT 21.02
  • OpenWRT 22.03

Requirements

Some modules optionally require a way to generate SHA1 hashes or encode data Base64. In case of Base64, there is a very slow hexdump | awk implementation included. For SHA1 there is no workaround. The modules will try to find usable system commands for SHA1 (sha1sum, openssl) and Base64 (base64, openssl, workaround) when needed. If no usable commands are found, most things will still work, but the fetch module for example has to be run with validate_checksum: no, will always download the file and return changed: yes. Therefore it is recommended to install coreutils-sha1sum and coreutils-base64, if the commands are not already provided by busybox. The role does that automatically by default (see below).

Role Variables

openwrt_install_recommended_packages:
    Checks for some commands and installs the corresponding packages if they are
    missing. See requirements above. (default: yes)

openwrt_scp_if_ssh:
    Whether to use scp instead of sftp for OpenWRT systems. Value can be `yes`,
    `no` or `smart`. Ansible defaults to `smart` but this role defaults to `yes`
    because OpenWRT does not offer sftp by default. (default: yes)

openwrt_remote_tmp:
    Ansibles remote_tmp setting for OpenWRT systems. Defaults to /tmp to avoid
    flash wear on target device. (default: /tmp)

openwrt_wait_for_connection, openwrt_wait_for_connection_timeout:
    Whether to wait for the host (default: yes) and how long (300) after a
    network or wifi restart (see handlers).

openwrt_ssh, openwrt_scp, openwrt_ssh_host, openwrt_ssh_user, openwrt_user_host:
    Helper shortcuts to do things like
    "command: {{ openwrt_scp }} {{ openwrt_user_host|quote }}:/etc/rc.local /tmp"

Example Playbook

Inventory:

[aps]
ap1.example.com
ap2.example.com
ap3.example.com

[routers]
router1.example.com

[openwrt:children]
aps
routers

Playbook:

- hosts: openwrt
  roles:
    - gekmihesg.openwrt
  tasks:
    - name: copy openwrt image
      command: "{{ openwrt_scp }} image.bin {{ openwrt_user_host|quote }}:/tmp/sysupgrade.bin"
      delegate_to: localhost
    - name: start sysupgrade
      nohup:
        command: sysupgrade -q /tmp/sysupgrade.bin
    - name: wait for reboot
      wait_for_connection:
        timeout: 300
        delay: 60
    - name: install mdns
      opkg:
        name: mdns
        state: present
    - name: enable and start mdns
      service:
        name: mdns
        state: started
        enabled: yes
    - name: copy authorized keys
      copy:
        src: authorized_keys
        dest: /etc/dropbear/authorized_keys
    - name: revert pending changes
      uci:
        command: revert
    - name: configure wifi device radio0
      uci:
        command: set
        key: wireless.radio0
        value:
          phy: phy0
          type: mac80211
          hwmode: 11g
          channel: auto
    - name: configure wifi interface
      uci:
        command: section
        config: wireless
        type: wifi-iface
        find_by:
          device: radio0
          mode: ap
        value:
          ssid: MySSID
          encryption: psk2+ccmp
          key: very secret
    - name: commit changes
      uci:
        command: commit
      notify: reload wifi

Running the modules outside of a playbook is possible like this:

$ export ANSIBLE_LIBRARY=~/.ansible/roles/gekmihesg.openwrt/library
$ export ANSIBLE_VARS_PLUGINS=~/.ansible/roles/gekmihesg.openwrt/vars_plugins
$ ansible -i openwrt-hosts -m setup all

License

GNU General Public License v3.0 (see https://www.gnu.org/licenses/gpl-3.0.txt)

Developing

Writing custom modules for this framework isn't to hard. The modules are wrapped into a wrapper script, that provides some common functions for parameter parsing, json handling, response generation, and some more. All modules must match openwrt_<module_name>.sh. If module_name is not one of Ansibles core modules, there must also be a <module_name>.py. This does not have to have any functionality (it may have some for non OpenWRT systems) and can contain the documentation.

Make sure to install the requirements.txt packages in your virtual environment and, with the venv activated, run:

$ molecule test

before commiting and submitting your PR.

Writing tests for your new module is also highly recommended.

ansible-openwrt's People

Contributors

aiyionprime avatar atemp avatar baldurmen avatar blind-oracle avatar dhurley94 avatar gekmihesg avatar nopdotcom avatar pallxk avatar psliwka avatar puck avatar russoz avatar s-hamann avatar westurner avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ansible-openwrt's Issues

How to fetch file from remote OpenWrt?

This is not an issue, but a query.

"copy" works flawlessly from ansible host to remote OpenWrt device. I want to copy a file (configuration backup generated by sysupgrade -b) to my ansible host.

Any suggestions?

Thanks in advance.

How to use this galaxy role from ansible-core 2.13.4?

At https://github.com/rgl/my-openwrt-ansible-playbooks I'm trying to use this role with ansible-core 2.13.4, but its failing:

$ ./ansible-playbook.sh --limit=openwrt lab.yml
TASK [Gathering Facts] *********************************************************
fatal: [openwrt]: FAILED! => changed=false 
  ansible_facts: {}
  failed_modules:
    ansible.legacy.setup:
      ansible_facts:
        discovered_interpreter_python: /usr/bin/python
      failed: true
      module_stderr: |-
        /bin/sh: /usr/bin/python: not found
      module_stdout: ''
      msg: |-
        The module failed to execute correctly, you probably need to set the interpreter.
        See stdout/stderr for the exact error
      rc: 127
      warnings:
      - No python interpreters found for host openwrt (tried ['python3.10', 'python3.9', 'python3.8', 'python3.7', 'python3.6', 'python3.5', '/usr/bin/python3', '/usr/libexec/platform-python', 'python2.7', '/usr/bin/python', 'python'])
  msg: |-
    The following modules failed to execute: ansible.legacy.setup

I can see there was some issues (#25 and #31) about ansible-core, but for some reason they do not seem to work for me. Can you please help?

copy sets +x bit on the file at destination

When I use "copy" command, it copies the file successfully but sets the executable bit on it:
-rwxr--r-- 1 root root 1597 Jul 19 18:28 squid.conf

While the source file doesn't have it:
-rw-r--r-- 1 root root 1597 Jul 19 17:53 squid.conf

Also, how can we pass on the argument to have required modes set on the file copied?

unable to run , unknown encoding: base64

when I try to run the playbook , there is an error message

fatal: [jm-router.abc.com]: UNREACHABLE! => {"changed": false, "msg": "EOF on stream; last 300 bytes received: u'Traceback (most recent call last):\\n  File \"<string>\", line 1, in <module>\\nLookupError: unknown encoding: base64\\n'", "unreachable": true}
	to retry, use: --limit @/home/mini/ansible/playbook.test.retry

do you have any ideas ?

We do not verify the presence of opkg cache files for update

When openwrt_remote_opkg_lists_dir is placed at /tmp/opkg-lists (the
default), it is recreated on each boot. However, when any opkg command
is run -- including e.g. opkg list-installed -- that directory is
created and left empty.

On such a router, the first time an opkg command is run, the
openwrt_remote_opkg_lists_dir is given mtime <= 86400 (at least for
the next 24h), suggesting to the default ansible-openwrt's tasks that
an opkg update has been issued recently when one has not. This seems
to happen when an Ansible check-mode run is performed right after a
router is booted.

ansible_remote_tmp is usually /root, which is bad

Hey there,

Would it be possible to have this role default ansible_remote_tmp to /tmp instead of /root? The default beahvior wears down flash.

I'm not sure if any parts of this role require persistence of files, so I'm not sure if /tmp is safe.

Make the code ansible-lint happy

Can this be ansible-lint happy?

For example, with ansible-lint 6.5.2 (and ansible-core 2.13.4), this is the result:

$ ./ansible-lint.sh --offline --parseable home.yml
WARNING  Listing 31 violation(s) that are fatal
../usr/share/ansible/roles/gekmihesg.openwrt/defaults/main.yml:2: yaml: truthy value should be one of  (yaml[truthy])
../usr/share/ansible/roles/gekmihesg.openwrt/defaults/main.yml:4: yaml: truthy value should be one of  (yaml[truthy])
../usr/share/ansible/roles/gekmihesg.openwrt/defaults/main.yml:12: yaml: truthy value should be one of  (yaml[truthy])
../usr/share/ansible/roles/gekmihesg.openwrt/defaults/main.yml:14: yaml: truthy value should be one of  (yaml[truthy])
../usr/share/ansible/roles/gekmihesg.openwrt/handlers/main.yml:2: name: All names should start with an uppercase letter. (name[casing])
../usr/share/ansible/roles/gekmihesg.openwrt/handlers/main.yml:8: name: All names should start with an uppercase letter. (name[casing])
../usr/share/ansible/roles/gekmihesg.openwrt/handlers/main.yml:14: name: All names should start with an uppercase letter. (name[casing])
../usr/share/ansible/roles/gekmihesg.openwrt/handlers/main.yml:20: fqcn-builtins: Use FQCN for builtin actions.
../usr/share/ansible/roles/gekmihesg.openwrt/handlers/main.yml:20: name: All names should start with an uppercase letter. (name[casing])
../usr/share/ansible/roles/gekmihesg.openwrt/meta/main.yml:1: role-name: Role name gekmihesg.openwrt does not match ``^*$`` pattern.
../usr/share/ansible/roles/gekmihesg.openwrt/meta/main.yml:1: schema: 2.4 is not of type 'string' (schema[meta])
../usr/share/ansible/roles/gekmihesg.openwrt/meta/main.yml:9: yaml: missing starting space in comment (yaml[comments])
../usr/share/ansible/roles/gekmihesg.openwrt/meta/main.yml:10: yaml: missing starting space in comment (yaml[comments])
../usr/share/ansible/roles/gekmihesg.openwrt/meta/main.yml:13: yaml: missing starting space in comment (yaml[comments])
../usr/share/ansible/roles/gekmihesg.openwrt/meta/main.yml:17: yaml: wrong indentation: expected 4 but found 2 (yaml[indentation])
../usr/share/ansible/roles/gekmihesg.openwrt/tasks/main.yml:2: fqcn-builtins: Use FQCN for builtin actions.
../usr/share/ansible/roles/gekmihesg.openwrt/tasks/main.yml:2: name: All names should start with an uppercase letter. (name[casing])
../usr/share/ansible/roles/gekmihesg.openwrt/tasks/package.yml:2: fqcn-builtins: Use FQCN for builtin actions.
../usr/share/ansible/roles/gekmihesg.openwrt/tasks/package.yml:2: name: All names should start with an uppercase letter. (name[casing])
../usr/share/ansible/roles/gekmihesg.openwrt/tasks/package.yml:9: name: All names should start with an uppercase letter. (name[casing])
../usr/share/ansible/roles/gekmihesg.openwrt/tasks/packages.yml:2: fqcn-builtins: Use FQCN for builtin actions.
../usr/share/ansible/roles/gekmihesg.openwrt/tasks/packages.yml:2: name: All names should start with an uppercase letter. (name[casing])
../usr/share/ansible/roles/gekmihesg.openwrt/tasks/packages.yml:7: fqcn-builtins: Use FQCN for builtin actions.
../usr/share/ansible/roles/gekmihesg.openwrt/tasks/packages.yml:7: name: All names should start with an uppercase letter. (name[casing])
../usr/share/ansible/roles/gekmihesg.openwrt/tasks/packages.yml:14: name: All names should start with an uppercase letter. (name[casing])
../usr/share/ansible/roles/gekmihesg.openwrt/tasks/packages.yml:17: yaml: truthy value should be one of  (yaml[truthy])
../usr/share/ansible/roles/gekmihesg.openwrt/tasks/packages.yml:24: fqcn-builtins: Use FQCN for builtin actions.
../usr/share/ansible/roles/gekmihesg.openwrt/tasks/packages.yml:24: name: All names should start with an uppercase letter. (name[casing])
../usr/share/ansible/roles/gekmihesg.openwrt/tasks/packages.yml:38: fqcn-builtins: Use FQCN for builtin actions.
../usr/share/ansible/roles/gekmihesg.openwrt/tasks/packages.yml:38: name: All names should start with an uppercase letter. (name[casing])
home.yml:7: fqcn-builtins: Use FQCN for builtin actions.
You can skip specific rules or tags by adding them to your configuration file:
# .config/ansible-lint.yml
warn_list:  # or 'skip_list' to silence them completely
  - experimental  # all rules tagged as experimental
  - fqcn-builtins  # Use FQCN for builtin actions.
  - yaml[comments]  # Violations reported by yamllint.
  - yaml[indentation]  # Violations reported by yamllint.
  - yaml[truthy]  # Violations reported by yamllint.

Finished with 17 failure(s), 14 warning(s) on 7 files.

Would a PR to fix this be acceptable?

"sftp transfer mechanism failed" warnings

When running my role via Ansible 2.9.4, I received the warning:

[WARNING]: sftp transfer mechanism failed on [router18]. Use ANSIBLE_DEBUG=1 to see detailed information

The above line appeared 5 times in the log.

I added scp_if_ssh = True under the [ssh_connection] heading in ansible.cfg (source) and the warning messages stopped. The role seems to work properly with or without this fix.

Alternatively, I think installing openssh-sftp-server on the guest would fix this (untested).

Perhaps this could be mentioned in README.md or similar.

`service` fails on second host

I'm using

- name: disable services
  service:
    name: "{{ item }}"
    enabled: no
    state: stopped
  loop:
    - odhcpd
    - dnsmasq
    - firewall

for my two wifi access points.

Whenever I run ansible targeting both hosts, the task succeeds on the first AP, but always fails on the second with:

failed: [ap2] (item=dnsmasq) => {
    "ansible_loop_var": "item",
    "changed": false,
    "item": "dnsmasq",
    "rc": 127
}

MSG:

The module failed to execute correctly, you probably need to set the interpreter.
See stdout/stderr for the exact error


MODULE_STDOUT:

/bin/sh: /usr/bin/python: not found

If I run ansible with --limit either one of the two APs (tried both) it works flawlessly.

ansible 2.12.17 on Fedora f36 with latest gekmihesg.ansible-openwrt

Cannot invoke uci task with rootfs Docker image (ash shell)

Hi,

I got a quite specific problem. I'm trying to bulletproof my Ansible playbooks before reinstalling a route, and was thinking to use your module combined with Docker and ideally molecule. My attempt have failed on a what it seems a bug of the uci task towards the rootfs Docker image.

This very simple playbook:

- hosts: openwrt
  roles:
    - gekmihesg.openwrt
  tasks:
    - name: Show UCI changes
      shell: uci show luci
    - name: Show UCI changes
      uci:
        command: show
        config: luci

Produces the following ouptut (truncated to the end for the sake of brevity and keeping the relevant parts):

TASK [Show UCI] ******************************************************************************************************************************************************************************************************************************
changed: [localhost] => {"changed": true, "cmd": "uci show luci", "delta": "0:00:00.000000", "end": "2021-04-18 13:13:21.000000", "rc": 0, "start": "2021-04-18 13:13:21.000000", "stderr": "", "stderr_lines": [], "stdout": "luci.main=core\nluci.main.lang='auto'\nluci.main.resourcebase='/luci-static/resources'\nluci.main.ubuspath='/ubus/'\nluci.main.mediaurlbase='/luci-static/material'\nluci.flash_keep=extern\nluci.flash_keep.uci='/etc/config/'\nluci.flash_keep.dropbear='/etc/dropbear/'\nluci.flash_keep.openvpn='/etc/openvpn/'\nluci.flash_keep.passwd='/etc/passwd'\nluci.flash_keep.opkg='/etc/opkg.conf'\nluci.flash_keep.firewall='/etc/firewall.user'\nluci.flash_keep.uploads='/lib/uci/upload/'\nluci.languages=internal\nluci.sauth=internal\nluci.sauth.sessionpath='/tmp/luci-sessions'\nluci.sauth.sessiontime='3600'\nluci.ccache=internal\nluci.ccache.enable='1'\nluci.themes=internal\nluci.themes.Bootstrap='/luci-static/bootstrap'\nluci.themes.Material='/luci-static/material'\nluci.apply=internal\nluci.apply.rollback='90'\nluci.apply.holdoff='4'\nluci.apply.timeout='5'\nluci.apply.display='1.5'\nluci.diag=internal\nluci.diag.dns='openwrt.org'\nluci.diag.ping='openwrt.org'\nluci.diag.route='openwrt.org'", "stdout_lines": ["luci.main=core", "luci.main.lang='auto'", "luci.main.resourcebase='/luci-static/resources'", "luci.main.ubuspath='/ubus/'", "luci.main.mediaurlbase='/luci-static/material'", "luci.flash_keep=extern", "luci.flash_keep.uci='/etc/config/'", "luci.flash_keep.dropbear='/etc/dropbear/'", "luci.flash_keep.openvpn='/etc/openvpn/'", "luci.flash_keep.passwd='/etc/passwd'", "luci.flash_keep.opkg='/etc/opkg.conf'", "luci.flash_keep.firewall='/etc/firewall.user'", "luci.flash_keep.uploads='/lib/uci/upload/'", "luci.languages=internal", "luci.sauth=internal", "luci.sauth.sessionpath='/tmp/luci-sessions'", "luci.sauth.sessiontime='3600'", "luci.ccache=internal", "luci.ccache.enable='1'", "luci.themes=internal", "luci.themes.Bootstrap='/luci-static/bootstrap'", "luci.themes.Material='/luci-static/material'", "luci.apply=internal", "luci.apply.rollback='90'", "luci.apply.holdoff='4'", "luci.apply.timeout='5'", "luci.apply.display='1.5'", "luci.diag=internal", "luci.diag.dns='openwrt.org'", "luci.diag.ping='openwrt.org'", "luci.diag.route='openwrt.org'"]}

TASK [Show UCI changes] **********************************************************************************************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "command": "show", "config": "luci", "msg": "uci show luci: /tmp/ansible-tmp-1618751601.2301168-510587-135474291645346/AnsiballZ_openwrt_uci.sh: eval: line 1: /sbin/uci/: not found", "result": "/tmp/ansible-tmp-1618751601.2301168-510587-135474291645346/AnsiballZ_openwrt_uci.sh: eval: line 1: /sbin/uci/: not found"}

As you can see, the uci task fails with the message /sbin/uci/: not found whereas a simple shell task invoking uci works flawlessly.

I suspect something linked to the build of the uci variable in the init() function of openwrt_uci.sh, but I haven't been successful till now in finding the cause.

I can confirm that the naive approach of replacing uci="/sbin/uci${state_path:+ -P '${state_path//'/'\\''}'}" with uci="/sbin/uci". So far, I don't really understand all the state_path thing.

I have executed the following commands in the Docker container shell to help diagnose the problem, but apparently, directly invoked from the shell, all works as expected:

root@209c55658f4d:/# state_path="$(mktemp -d)"
root@209c55658f4d:/# echo $state_path
/tmp/tmp.FeFFIE
root@209c55658f4d:/# echo "/sbin/uci${state_path:+ -P '${state_path//'/'\\''}'}"
/sbin/uci -P '/tmp/tmp.FeFFIE

Support for Ansible-core >= 2.10

Hello there, I've tried to use this role with Ansible-core 2.12.6:

ansible-playbook [core 2.12.6]
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/joel/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3/dist-packages/ansible
  ansible collection location = /home/joel/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible-playbook
  python version = 3.10.4 (main, Apr  2 2022, 09:04:19) [GCC 11.2.0]
  jinja version = 3.0.3
  libyaml = True

And I get this failure:

task path: /home/joel/.ansible/roles/gekmihesg.openwrt/tasks/main.yml:16
redirecting (type: modules) ansible.builtin.opkg to community.general.opkg
fatal: [hostname]: FAILED! => {"reason": "couldn't resolve module/action 'opkg'. This often indicates a misspelling, missing collection, or incorrect module path.\n\nThe error appears to be in '/home/joel/.ansible/roles/gekmihesg.openwrt/tasks/packages.yml': line 14, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- name: update opkg cache\n  ^ here\n"}

I tried changing the opkg module in both the role code and my own Ansible code but it seems that the gekmihesg.openwrt.opkg module can't be found either. I'm guessing somewhere in the refactor to collections the logic that this role uses changed? I'd love to help to submit a patch, but unfortunately I'm lost as to where to begin right now.

Any advice/direction appreciated!

How does the stuff in library/ work

Hi,
I am using your openwrt role to manage my router. Now I'd like to implement the argv functionality of the command command, and I looked at the shell scripts in library/. It seems like there is some wrapper code from python which loads the corresponding shell script, passes the argument variables, executes main() and extracts the result variables. Can you point me to where this happens? Is it an official ansible functionality or implemented by you?

Thanks,
Jonathan

Feature Request: opkg: support upgrading all packages

For example, with the yum module:

yum:                                                                                                                                                                                                                                     
  name: '*'                                                                                                                                                                                                                              
  state: latest                                                                                                                                                                                                                          

It would be nice to be able to do the same with opkg:

opkg:                                                                                                                                                                                                                                     
  name: '*'                                                                                                                                                                                                                              
  state: latest                                                                                                                                                                                                                          

Looking at the code, probably wouldn't be too bad to special case *, do opkg list-upgradable and feed that into install_packages.

Adding better support for opkg

We are trying to install packages via their .ipk - for offline package installations

Ensuring that the list of dependencies is properly organized to meet the required order of installation.

What this tries to resolve is the following

    - opkg:
        name: "/tmp/snmp/libpcre_8.45-1_mips_24kc.ipk"
        state: present
        force: overwrite

Basically, it installs fine, but the module always returns fail, as it is doing:

opkg status /tmp/snmp/libpcre_8.45-1_mips_24kc.ipk

As per:

query_package() {
    [ -n "$(opkg status "$1")" ]
}

So to make it "work" - I modified install_packages() function inside library/openwrt_opkg.sh like so:

install_packages() {
    local _IFS pkg pkg_name install_output
    _IFS="$IFS"; IFS=","; set -- $name; IFS="$_IFS"
    for pkg; do
        pkg_name="$pkg"
        if [[ "$pkg" == *.ipk ]]; then
            pkg_name=$(basename "$pkg" .ipk | sed -r 's/_.*//')
        fi
        ! query_package "$pkg_name" || continue
        [ -n "$_ansible_check_mode" ] || {
            try opkg install$force $nodeps "$pkg"
            query_package "$pkg_name" || fail "failed to install $pkg: $_result"
        }
        changed
    done
}

Anyway, this is working well now, installing and returning OK if installed already, changed if it's really changed etc...

@gekmihesg seems to be AWOL

bit of a shame 😞

great repo

Struggling to use absent via key

First of all, thanks for this awesome piece of work, I just deployed my vlan config to my openwrt switches via ansible, and it's awesome :D

One thing I'm struggling with is the absent function in the uci module though.

I've got a default section like this on my devices:

network.wan_vlan=bridge-vlan
network.wan_vlan.device='switch'
network.wan_vlan.vlan='1'

I could delete it by value as you did in your two examples:

    - name: drop default bridge-vlan wan_vlan
      uci:
        command: absent
        config: network
        type: bridge-vlan
        find_by:
          vlan: 1

In case I ever wanted to use vlan one for something else, this would result in a non-idempotent mess.

I think I'm just using it wrong, but how would I correctly reference the option title wan_vlan?

I thought about this version (mimicing the syntax of section), which is apparently not right:

- name: drop default bridge-vlan wan_vlan
      uci:
        command: absent
        config: network
        type: bridge-vlan
        name: wan_vlan

Thanks!

Wireless setup doesn't work with multiple SSIDs on one radio

It's not an issue, but more a "help wanted":

I try to get a idempotent play which configure more than one SSID on a radio:

- name: configure wifi device radio0
  uci:
    command: set
    key: wireless.radio0
    value:
      type: mac80211
      hwmode: 11a
      country: DE
      channel: "{{ hostvars[inventory_hostname].channel_5G }}"
      legacy_rates: 0
      htmode: "{{ hostvars[inventory_hostname].htmode_5G }}"
      disabled: 0

- name: configure wifi device radio1
  uci:
    command: set
    key: wireless.radio1
    value:
      type: mac80211
      hwmode: 11g
      country: DE
      channel: "{{ hostvars[inventory_hostname].channel_2G4 }}"
      legacy_rates: 0
      htmode: "{{ hostvars[inventory_hostname].htmode_2G4 }}"
      disabled: 0

- name: configure 5G wifi interface
  uci:
    command: section
    config: wireless
    type: wifi-iface 'network1_radio0'
    find_by:
      device: radio0
      mode: ap
    value:
      ssid: ssid_12345
      network: lan
      encryption: psk2+ccmp
      key: geheim
      ieee80211r: 1
      mobility_domain: abcd
      ft_over_ds: 1
      ft_psk_generate_local: 1
      isolate: 1

- name: configure 2.4G wifi interface
  uci:
    command: section
    config: wireless
    type: wifi-iface 'network1_radio1'
    find_by:
      device: radio1
      mode: ap
    value:
      ssid: ssid_12345
      network: lan
      encryption: psk2+ccmp
      key: geheim
      ieee80211r: 1
      mobility_domain: abcd
      ft_over_ds: 1
      ft_psk_generate_local: 1
      isolate: 1

- name: configure 2.4G wifi guest interface
  uci:
    command: section
    config: wireless
    type: wifi-iface 'network2_radio1'
    find_by:
      device: radio1
      mode: ap
    value:
      ssid: ssid_ABCD
      network: lan
      encryption: psk2+ccmp
      key: geheim

Apart from this syntax doesn't work: How to I do it?

Consider refactoring with `ucode`

Hi there,

After trying to come up with a playbook solution to ensure only the wifi-ifaces I've created exist, I've found that ansible-openwrt's remote-only use of UCI is pretty restrictive. It would be easier to deal with configuration state if we could manage some of it locally using e.g. set_fact, but since uci output is hard to parse, that goes out of the window without some rather nasty Python.

We could fix this with Lua's UCI bindings, but even cleaner than that would be ucode, which has bindings for UCI (among other things) already in-place.

The only drawback I can see for now is availability -- as it stands ucode is only pre-installed on 22.03, and isn't yet being compiled or distributed for 21.02, 19.07, etc.

An example snippet of ucode dumping /etc/config/wireless to JSON would be:

require("uci");

cursor = global.modules.uci.cursor();
cursor.load("wireless");

printf("%.J\n", cursor.get_all("wireless"));

This can be run e.g. as

$ ucode -e 'require("uci"); cursor=global.modules.uci.cursor(); cursor.load("wireless"); printf("%.J\n", cursor.get_all("wireless"));'
{
	"radio0": {
		".anonymous": false,
		".type": "wifi-device",
		".name": "radio0",
		".index": 0,
		"type": "mac80211",
		"path": "ffe0a000.pcie/pcia000:02/a000:02:00.0/a000:03:00.0",
		"band": "5g",
		"htmode": "VHT80",
		"channel": "48",
		"country": "US",
		"disabled": "0"
	},
:
	"cfg063579": {
		".anonymous": true,
		".type": "wifi-iface",
		".name": "cfg063579",
		".index": 5,
		"ifname": "netgear2",
		"mode": "ap",
		"device": "radio0",
		"network": "lan",
		"ssid": "NETGEAR52",
		"encryption": "psk2",
		"key": "xxx",
		"disabled": "0"
	}
}

I can use snippets of ucode to feed playbooks for now, but you may want to consider refactoring using ucode to make maintenance easier, at least once it's officially available.

Thanks for your time :^)

uci module breaks on openwrt 18.06 when --diff is used

uci module produces quite large output when used in combination with --diff flag. On openwrt 18.06, it seems to trigger some nasty bug in Dropbear, which I've reported in openwrt bug tracker.

Opening an issue here to let all ansible-openwrt users know about the problem, and possibly gather some workaroud ideas.

Right now, the only workaround I've found is to replace Dropbear with OpenSSH on all devices.

uci add and add_list commands issue

I encountered several errors when configuring with uci

1 playbook, add wireguard interface:

- name: add WG interface
  uci:
    command: add
    type: network
    key: network.wg0
TASK [add WG interface] **********************************************************************************************************************
fatal: [192.168.10.10]: FAILED! => {"changed": false, "command": "add", "config": "network", "msg": "uci add network.wg0 network: /sbin/uci: Entry not found", "result": "/sbin/uci: Entry not found", "section": "wg0"}

I think there is a issue with the command line syntax. Must be uci add network network.wg0

2 playbook, configuring wireguard interface. add_list command:

- name: add list
  uci:
    command: add_list
    key: network.wg0
    value:
      addresses: "192.168.1.2/24"
TASK [add list] ******************************************************************************************************************************
fatal: [192.168.10.10]: FAILED! => {"changed": false, "command": "add_list", "config": "network", "msg": "key and value required for add_list", "section": "wg0"}

I do this after setting up the interface. The command uci add_list network.wg0.addresses=192.168.1.2/24 is processed without errors.
Did I specify both parameters correctly?

OpenWrt release 18.06.1

get multiple sections

Hello, Is there a possibility to get multiple sections of some type or matching some options so one can register the result into a variable so one can e.g. use the variable in the next task with some additional logic/filtering?

Simple use case: Delete all sections of type 'interface' from config 'network' except the one named 'loopback'.

Thanks

fatal: "Failed to connect to the host via scp"

Hi, I am running ansible 2.15 on Debian Bookworm (OpenSSH 9.2p1)
The host runs openwrt-23.05.0-rc2

Right now I am unable to even run any playbook, as the included tasks already fail.

ansible [core 2.15.3]
   config file = None
   configured module search path = ['/home/josha/.ansible/roles/gekmihesg.openwrt/library']
   ansible python module location = /home/josha/venv/lib/python3.11/site-packages/ansible
   ansible collection location = /home/josha/.ansible/collections:/usr/share/ansible/collections
   executable location = /home/josha/venv/bin/ansible
   python version = 3.11.2 (main, Mar 13 2023, 12:18:29) [GCC 12.2.0] (/home/josha/venv/bin/python3)
   jinja version = 3.1.2
   libyaml = True

$ ssh -V
OpenSSH_9.2p1 Debian-2, OpenSSL 3.0.9 30 May 2023

$ ~/venv/bin/ansible-playbook ./Documents/router.yml -i ./Documents/inventory.conf

PLAY [openwrt] **************************************************************************************************************************************************************************************************

TASK [Gathering Facts] ******************************************************************************************************************************************************************************************
[WARNING]: sftp transfer mechanism failed on [192.168.1.1]. Use ANSIBLE_DEBUG=1 to see detailed information
[WARNING]: scp transfer mechanism failed on [192.168.1.1]. Use ANSIBLE_DEBUG=1 to see detailed information
ok: [192.168.1.1]

TASK [gekmihesg.openwrt : pin defaults] *************************************************************************************************************************************************************************
ok: [192.168.1.1]

TASK [gekmihesg.openwrt : install recommended packages] *********************************************************************************************************************************************************
included: /home/josha/.ansible/roles/gekmihesg.openwrt/tasks/packages.yml for 192.168.1.1

TASK [gekmihesg.openwrt : check whether opkg caches need update] ************************************************************************************************************************************************
fatal: [192.168.1.1]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via scp: ash: /usr/libexec/sftp-server: not found\nscp: Connection closed\r\n", "unreachable": true}

PLAY RECAP ******************************************************************************************************************************************************************************************************
192.168.1.1                : ok=3    changed=0    unreachable=1    failed=0    skipped=0    rescued=0    ignored=0

Any idea why or what details I should provide?

Kind regards, Josha

monkeypatch.py generates infinite recrusion

Currently when running code based on this role, I get the following error (Ansible 2.8.{3,4,5}):

Command:

ansible-playbook -i hosts ~/path/to/openwrt.yml -vvv

Relevant output:

The full traceback is:
Traceback (most recent call last):
  File "/usr/lib/python3.7/site-packages/ansible/executor/task_executor.py", line 111, in run
    item_results = self._run_loop(items)
  File "/usr/lib/python3.7/site-packages/ansible/executor/task_executor.py", line 371, in _run_loop
    res = self._execute(variables=task_vars)
  File "/usr/lib/python3.7/site-packages/ansible/executor/task_executor.py", line 664, in _execute
    result = self._handler.run(task_vars=variables)
  File "/usr/lib/python3.7/site-packages/ansible/plugins/action/normal.py", line 46, in run
    result = merge_hash(result, self._execute_module(task_vars=task_vars, wrap_async=wrap_async))
  File "/usr/lib/python3.7/site-packages/ansible/plugins/action/__init__.py", line 809, in _execute_module
    (module_style, shebang, module_data, module_path) = self._configure_module(module_name=module_name, module_args=module_args, task_vars=task_vars)
  File "/home/ghelling/config/mkgreg/roles/openwrt_wifi/filter_plugins/monkeypatch.py", line 28, in _configure_module
    self.__configure_module(module_name, module_args, task_vars)
  File "/home/ghelling/config/mkgreg/roles/openwrt_switch/filter_plugins/monkeypatch.py", line 28, in _configure_module
    self.__configure_module(module_name, module_args, task_vars)
  File "/home/ghelling/config/mkgreg/roles/openwrt_switch/filter_plugins/monkeypatch.py", line 28, in _configure_module
    self.__configure_module(module_name, module_args, task_vars)
  File "/home/ghelling/config/mkgreg/roles/openwrt_switch/filter_plugins/monkeypatch.py", line 28, in _configure_module
    self.__configure_module(module_name, module_args, task_vars)
  [Previous line repeated 968 more times]
  File "/home/ghelling/config/mkgreg/roles/openwrt_switch/filter_plugins/monkeypatch.py", line 22, in _configure_module
    openwrt_module = self._shared_loader_obj.module_loader.find_plugin('openwrt_' + module_name, '.sh')
  File "/usr/lib/python3.7/site-packages/ansible/plugins/loader.py", line 502, in find_plugin
    plugin = self._find_plugin(name, mod_type=mod_type, ignore_deprecated=ignore_deprecated, check_aliases=check_aliases, collection_list=collection_list)
  File "/usr/lib/python3.7/site-packages/ansible/plugins/loader.py", line 418, in _find_plugin
    return self._find_plugin_legacy(name, ignore_deprecated, check_aliases, suffix)
  File "/usr/lib/python3.7/site-packages/ansible/plugins/loader.py", line 440, in _find_plugin_legacy
    for path in (p for p in self._get_paths() if p not in self._searched_paths and os.path.isdir(p)):
  File "/usr/lib/python3.7/site-packages/ansible/plugins/loader.py", line 440, in <genexpr>
    for path in (p for p in self._get_paths() if p not in self._searched_paths and os.path.isdir(p)):
  File "/usr/lib64/python3.7/genericpath.py", line 42, in isdir
    st = os.stat(s)
RecursionError: maximum recursion depth exceeded while calling a Python object

fatal: [genesis.greg.thehellings.com]: FAILED! => 
  msg: Unexpected failure during module execution.
  stdout: ''

Example of using openwrt_lineinfile

Hi,

Would you have an example for replacing ansible.builtin lineinfile with your package?

Consider the below

- name: Ensure auth creds are in config
    ansible.builtin.lineinfile:
      path: 'surfshark_udp.ovpn'
      regexp: '^auth-user-pass'
      line: auth-user-pass /etc/openvpn/surfshark_udp.auth

Does it require "command: {{ openwrt_lineinfile }}" ? what is the pattern expected

is check_mode working ?

in general, is check mode support by this role ? and in particular: does it work with uci set commands ?

starting or stopping service

I'm trying to start or stop a service, but the is_running function is not working for me.
"/etc/init.d/uhttpd running" is always returning 0.
It seems that rc.common is always returning 0 for is_running in OpenWrt 19.07, from 21.02 there is a change but now it is always returning 1.
I can add the "pattern: uhttpd" in playbook, this is working.
Maybe because "running" had to be implemented per init.d script?
Do I have to add the pattern option to every service in the playbook?

mux_client_read_packet: read header failed: Broken pipe

I tryed your module on following OpenWrt system

NAME="OpenWrt"
VERSION="2.0.0"
ID="openwrt"
ID_LIKE="lede openwrt"
PRETTY_NAME="OpenWrt 2.0.0"
VERSION_ID="2.0.0"
HOME_URL="%u"
BUG_URL="http://bugs.openwrt.org/"
SUPPORT_URL="http://forum.lede-project.org/"
BUILD_ID="r7402+17-0f543883cd"
LEDE_BOARD="sunxi/cortexa7"
LEDE_ARCH="arm_cortex-a7_neon-vfpv4"
LEDE_TAINTS="no-all glibc busybox"
LEDE_DEVICE_MANUFACTURER="Kerberos"
LEDE_DEVICE_MANUFACTURER_URL="www.x-monitor.it"
LEDE_DEVICE_PRODUCT="Generic"
LEDE_DEVICE_REVISION="1.0.0"
LEDE_RELEASE="OpenWrt 2.0.0 1.0"

using this simple playbook:


---
- hosts: rtus_poste

  roles:
    - ansible-openwrt

  tasks:

    - name: Commit
      uci:
         command: commit

Ansible version 2.9.6 on Linux Mint, I get this error:

fatal: [rtu_poste]: FAILED! => {
    "changed": false,
    "module_stderr": "OpenSSH_8.2p1 Ubuntu-4ubuntu0.1, OpenSSL 1.1.1f  31 Mar 2020\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug2: resolve_canonicalize: hostname 10.29.9.42 is address\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 18875\r\ndebug3: mux_client_request_session: session request sent\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 0\r\nShared connection to 10.29.9.42 closed.\r\n",
    "module_stdout": "",
    "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error",
    "rc": 0
}

Not compatible with ansible-core `2.16.0` version

Hello,

I'm aware that this repo is not really maintained but I would like to know if someone here has successfully used this role without installing the python interpreter on its openwrt devices ?
I'm asking this because i'm pretty new with Ansible and I can't get this role to work without the following error :

> ansible-playbook --limit belkin-ap playbooks/openwrt.yml

PLAY [openwrt] ***********************************************************************************************************************************

TASK [gekmihesg.openwrt : pin defaults] **********************************************************************************************************
ok: [belkin-ap]

TASK [gekmihesg.openwrt : install recommended packages] ******************************************************************************************
included: /home/lweber/code/gitlab.com/neonox31/homelab/ansible/roles/gekmihesg.openwrt/tasks/packages.yml for belkin-ap

TASK [gekmihesg.openwrt : check whether opkg caches need update] *********************************************************************************
[WARNING]: No python interpreters found for host belkin-ap (tried ['python3.12', 'python3.11', 'python3.10', 'python3.9', 'python3.8',
'python3.7', 'python3.6', '/usr/bin/python3', '/usr/libexec/platform-python', 'python2.7', '/usr/bin/python', 'python'])
fatal: [belkin-ap]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "module_stderr": "/bin/sh: /usr/bin/python: not found\n", "module_stdout": "", "msg": "The module failed to execute correctly, you probably need to set the interpreter.\nSee stdout/stderr for the exact error", "rc": 127}

PLAY RECAP ***************************************************************************************************************************************
belkin-ap                  : ok=2    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   

Here is my config :

inventory

[pve]
pve-0

[openwrt]
living-room-ap
garage-ap
belkin-ap

playbooks/openwrt.yml

- hosts: openwrt
  roles:
    - gekmihesg.openwrt
  gather_facts: false

requirements.yml

---
collections:
  - name: ansible.utils
  - name: community.general
roles:
  - name: gekmihesg.openwrt
    scm: git
    src: https://github.com/gekmihesg/ansible-openwrt.git

group_vars/openwrt/common.yml

ansible_scp_extra_args: "-O"

And finally the ansible version :

❯ ansible --version
ansible [core 2.16.0]
  config file = /home/lweber/code/gitlab.com/neonox31/homelab/ansible/ansible.cfg
  configured module search path = ['/home/lweber/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/lweber/.asdf/installs/ansible/9.0.1/venv/lib/python3.12/site-packages/ansible
  ansible collection location = /home/lweber/code/gitlab.com/neonox31/homelab/ansible/collections
  executable location = /home/lweber/.asdf/installs/ansible/9.0.1/bin/ansible
  python version = 3.12.0 (main, Dec  3 2023, 14:34:25) [GCC 13.2.1 20230801] (/home/lweber/.asdf/installs/ansible/9.0.1/venv/bin/python3)
  jinja version = 3.1.2
  libyaml = True

Maybe I'm missing something ?

HOWTO: uci set a service

I'm trying to figure out what an ansible uci task entry would look to do the following command sequence. I've tried a bunch of different combinations but can't seem to get anything to work completely.

uci set ddns.foo=service
uci set ddns.foo.enabled=1
uci set ddns.foo.lookup_host=host
uci set ddns.foo.domain=domain
uci commit

expecting uci show ddns:

...
ddns.foo=service
ddns.foo.lookup_host='host'
ddns.foo.enabled='1'
ddns.foo.domain='domain'

Any help would be appreciated.

Ansible 2.10 compatibility issues

Lots of things not working any more on Ansible 2.10. For example:

  • command
  • script
  • service
$ ansible --version
ansible 2.10.2
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/x/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.8/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 3.8.5 (default, Jul 27 2020, 08:42:51) [GCC 10.1.0]

Example Playbooks and errors:

- hosts: openwrt
  roles:
    - gekmihesg.openwrt
PLAY [openwrt] ***********

TASK [Gathering Facts] ***
[WARNING]: sftp transfer mechanism failed on [openwrt.lan]. Use ANSIBLE_DEBUG=1 to see detailed information
fatal: [openwrt.lan]: FAILED! => {"ansible_facts": {}, "changed": false, "failed_modules": {"ansible.legacy.setup": {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "failed": true, "module_stderr": "Shared connection to openwrt.lan closed.\r\n", "module_stdout": "/bin/sh: /usr/bin/python: not found\r\n", "msg": "The module failed to execute correctly, you probably need to set the interpreter.\nSee stdout/stderr for the exact error", "rc": 127, "warnings": ["No python interpreters found for host openwrt.lan (tried ['/usr/bin/python', 'python3.7', 'python3.6', 'python3.5', 'python2.7', 'python2.6', '/usr/libexec/platform-python', '/usr/bin/python3', 'python'])"]}}, "msg": "The following modules failed to execute: ansible.legacy.setup\n"}

PLAY RECAP ***************
openwrt.lan     : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

- hosts: openwrt
  gather_facts: no
  roles:
    - gekmihesg.openwrt
PLAY [openwrt] *****************************************************

TASK [gekmihesg.openwrt : pin defaults] ****************************
ok: [openwrt.lan]

TASK [gekmihesg.openwrt : install recommended packages] ************
included: /home/lxk/.ansible/roles/gekmihesg.openwrt/tasks/packages.yml for openwrt.lan

TASK [gekmihesg.openwrt : check whether opkg caches need update] ***
ok: [openwrt.lan]

TASK [gekmihesg.openwrt : get current system time] *****************
[WARNING]: No python interpreters found for host openwrt.lan (tried ['/usr/bin/python', 'python3.7', 'python3.6', 'python3.5', 'python2.7', 'python2.6', '/usr/libexec/platform-python', '/usr/bin/python3', 'python'])
fatal: [openwrt.lan]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "module_stderr": "/bin/sh: /usr/bin/python: not found\n", "module_stdout": "", "msg": "The module failed to execute correctly, you probably need to set the interpreter.\nSee stdout/stderr for the exact error", "rc": 127}

PLAY RECAP *********************************************************
openwrt.lan     : ok=3    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

- hosts: openwrt
  gather_facts: no
  roles:
    - name: gekmihesg.openwrt
      openwrt_install_recommended_packages: no
  tasks:
    - shell: echo a
PLAY [openwrt] ********************************************

TASK [gekmihesg.openwrt : pin defaults] *******************
ok: [openwrt.lan]

TASK [gekmihesg.openwrt : install recommended packages] ***
skipping: [openwrt.lan]

TASK [shell] **********************************************
[WARNING]: No python interpreters found for host openwrt.lan (tried ['/usr/bin/python', 'python3.7', 'python3.6', 'python3.5', 'python2.7', 'python2.6', '/usr/libexec/platform-python', '/usr/bin/python3', 'python'])
fatal: [openwrt.lan]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "module_stderr": "/bin/sh: /usr/bin/python: not found\n", "module_stdout": "", "msg": "The module failed to execute correctly, you probably need to set the interpreter.\nSee stdout/stderr for the exact error", "rc": 127}

PLAY RECAP ************************************************
openwrt.lan     : ok=1    changed=0    unreachable=0    failed=1    skipped=1    rescued=0    ignored=0

opkg list-installed

Hi,

How can i run the following command?

opkg list-installed | cut -f 1 -d ' ' > /root/installed_packages.txt

It is not possible with "command".

Thanks!

Unable to create a new selection with uci

Hi
I've been pounding my head ageist the wall for some hours now.
what I'm trying to acomplish is this;

uci set openvpn.vpn.enabled=1
uci set openvpn.vpn.config=/etc/openvpn/vpn.tcp.ovpn

in my play book I have this:

      uci:
        command: set
        key: openvpn
        value:
          vpn: "openvpn"

what I'm i missing???

How to install / use?

Hello,

I would like to use this project to configure my OpenWrt router, however I do not know how. How do I install this project? Is there any command (other than putting my OpenWrt hosts in the "openwrt" group) that I need to run before I can use it?

Monolithic Module Scripts

Hey Mike,
Very nice work here!!! I am curious about something. I was able to use the role when inventory points directly at the routers as noted in your docs, however, I've got a situation where i am updating router config using delegate_to: "{{groups['routers'][0] }}" .. which doesn't work so well with the current incarnation.

I cheated outrageously and used ANSIBLE_KEEP_REMOTE_FILES, AnsiballZ_uci.sh and re-created the openwrt_uci.sh as the result of the monkey-business ... and it works cleanly w/o having to use the plugins or wrapper.sh. Now my code looks like this:

- name: create host record in uci
  delegate_to: "{{groups['routers'][0]}}"
  openwrt_uci: 
    command: section
    config: dhcp
    type: host
    find_by: 
      name: "{{ inventory_hostname }}"
    value: 
      name: "{{ inventory_hostname }}"
      mac: "{{ macaddresses }}"
      ip: "{{ ipaddress | default(ansible_default_ipv4.address) }}"
  notify: uci commit

So I am curious .. why not just use the result as scripts in the ANSIBLE_LIBRARY directory to be used on the remote hosts, vs the manipulation to create them on the fly. I get the re-use of code argument.. however since this is a repo .. that should be easy to manage with a build script for the files.

If you're intersted in this approach .. then users can just replicate your library dir into theirs ( or include it, gilt it in ..etc.. ) and have a much simpler deployment... and I'll be happy to create that PR. Thanks.

Multiple sections with same identifier

Hi, Thanks for your nice role!

I am uci quite much but I got stuck when I tried to add another firewall zone.
Firewall zones are configured like this:

config zone
	option name		lan
	list   network		'lan'
	option input		ACCEPT
	option output		ACCEPT
	option forward		ACCEPT

config zone
	option name		wan
	list   network		'wan'
	list   network		'wan6'
	option input		REJECT
	option output		ACCEPT
	option forward		REJECT
	option masq		1
	option mtu_fix		1

firewall.@zone[0]=zone           
firewall.@zone[0].name='lan'      
firewall.@zone[0].network='lan'    
firewall.@zone[0].input='ACCEPT'                
firewall.@zone[0].output='ACCEPT'          
firewall.@zone[0].forward='ACCEPT'                                                                                                                                                      
firewall.@zone[1]=zone                       
firewall.@zone[1].name='wan'             
firewall.@zone[1].network='wan' 'wan6'
firewall.@zone[1].input='REJECT'
firewall.@zone[1].output='ACCEPT'
firewall.@zone[1].forward='REJECT'
firewall.@zone[1].masq='1'
firewall.@zone[1].mtu_fix='1'

And I cannot find a way how to add a new one - can you help me please ?

template not applying

I have this task to apply a template:

- name: "Template system configuration file: /etc/config/network"
  template:
    src: "templates/etc/config/network.j2"
    dest: "/etc/config/network"
    mode: "0600"
    owner: root
    group: root

At the moment the template is just a plain file without any jinja2 macros. Ansible always reports the file as changed but the file is never updated.

 ___________________________________________
/ TASK [Template system configuration file: \
\ /etc/config/network]                      /
 -------------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

changed: [ap3] => {"changed": true}
$ apt-cache policy ansible
ansible:
  Installed: 2.9.17-1ppa~bionic
  Candidate: 2.9.17-1ppa~bionic
  Version table:
 *** 2.9.17-1ppa~bionic 500
        500 http://ppa.launchpad.net/ansible/ansible/ubuntu bionic/main amd64 Packages
        100 /var/lib/dpkg/status
     2.5.1+dfsg-1ubuntu0.1 500
        500 http://gb.archive.ubuntu.com/ubuntu bionic-updates/universe amd64 Packages
        500 http://security.ubuntu.com/ubuntu bionic-security/universe amd64 Packages
     2.5.1+dfsg-1 500
        500 http://gb.archive.ubuntu.com/ubuntu bionic/universe amd64 Packages

Using --diff correctly shows the expected changes to the file.

Documentation on compatibility with OpenWRT

First of all, big thanks and respect for the works done & shared.
I just wanted to highlight that there is nothing about the minimal OpenWRT version we can run this project on.
Is it compatible with OpenWRT 15? or only latest (18) ?

Kind regards

A few more examples in README would be useful, eg "absent"

Reading library/uci.py, I see "absent" almost does what I want it to do:

# Find a matching wifi-iface and delete it.
- uci:
    command: absent
    config: wireless
    type: wifi-iface
    find:
      ssid: My SSID broken

But in my case, I want to delete "config interface 'wan'" and "config interface 'wan6'" which currently look like this:

config interface 'wan'
       option device 'wan'
       option proto 'dhcp'

config interface 'wan6'
       option device 'wan'
       option proto 'dhcpv6'

In both cases, device == wan, but I can't find a find option for just the name. I'm guessing you shouldn't even need to supply a find, because the thing to delete is the interface defined by name, but there's not enough detail in README to tell me what that syntax would be.

This only deletes the first entry for obvious reasons:

- name: delete LAN, WAN, WAN6 interfaces
  uci:
    command: absent
    config: network
    type: interface
    find:
      device: "{{ item }}"
  with_items:
    - "lan"
    - "wan"
    - "wan6"
  when: type == 'ap'

The translated UCI commands I'm looking to generate with that example are:

uci delete network.lan
uci delete network.wan
uci delete network.wan6

why is option required in named sections?

When using named sections option is required, but i am not sure why.

This playbook snippet

- name: test_section
  uci:
    command: section
    config: test_section
    type: the_type
    name: the_name
    value:
      val1: value one
      val2: value two

will give the following ansible error:

fatal: [192.168.1.1]: FAILED! => {"changed": false, "command": "section", "config": "test_section", "msg": "config, type and option required for section"}

But if i add an option

- name: test_section
  uci:
    command: section
    config: test_section
    type: the_type
    name: the_name
    value:
      val1: value one
      val2: value two
    option: the_option

it works and creates this on the openwrt host:

root@OpenWrt:/etc/config# cat test_section 

config the_type 'the_name'
	option val2 'value two'
	option val1 'value one'

root@OpenWrt:/etc/config# 

It seems the_option is unrelevant to the result.

I did some debugging and it seems the following uci commands are done:

/sbin/uci add test_section the_type
/sbin/uci rename test_section.cfg013b47=the_name
/sbin/uci set test_section.the_name.the_option=
/sbin/uci set test_section.the_name.val2=value two
/sbin/uci set test_section.the_name.val1=value one

So the_option is used, but it does not seem to do anything at all. Launched manually the uci commands do the same w/o the the_option line.

Is there any deeper sense in that or would it be possible to allow named sections w/o option?

ansible 2.10.6 - No python interpreters found

Hi!

I wanted to move from ansible 2.9.6 (Ubuntu 20.4) to a more current 20.10.6 ansible version using pyenv and ansible installed with pip but I failed with your module:

TASK [Gathering Facts] ***************************************************************************************************************************************************************
task path: /home/ansible/openwrt-facts.yml:11
fatal: [openwrt.lan]: FAILED! => {
    "ansible_facts": {},
    "changed": false,
    "failed_modules": {
        "ansible.legacy.setup": {
            "ansible_facts": {
                "discovered_interpreter_python": "/usr/bin/python"
            },
            "failed": true,
            "module_stderr": "Shared connection to openwrt.lan closed.\r\n",
            "module_stdout": "/bin/sh: /usr/bin/python: not found\r\n",
            "msg": "The module failed to execute correctly, you probably need to set the interpreter.\nSee stdout/stderr for the exact error",
            "rc": 127,
            "warnings": [
                "No python interpreters found for host openwrt.lan (tried ['/usr/bin/python', 'python3.7', 'python3.6', 'python3.5', 'python2.7', 'python2.6', '/usr/libexec/platform-python', '/usr/bin/python3', 'python'])"
            ]
        }
    },
    "msg": "The following modules failed to execute: ansible.legacy.setup\n"
}

The playbook for testing is really simple:

- name: OpenWrt setup devices
  hosts: openwrt
  roles:
    - gekmihesg.openwrt
  # gather_facts: true

  tasks:
    - debug: var=ansible_facts

When I set gather_facts to false it works but then I have no facts ...

I think the way ansible >= 2.10 runs vars plugin is the cause, see https://docs.ansible.com/ansible/latest/plugins/vars.html, but I did not figure out, what to set in ansible.cfg to whitelist the vars plugin.

I tried different variants of the following:

vars_plugins_enabled = host_group_vars,gekmihesg.openwrt.monkeypatch
run_vars_plugins = start

With the debian package of ansible version 2.9.6+dfsg-1 it works without a problem.

What else do I have to do to get this awesome module running with ansible 2.10.x?

Thanks in advance.

Template - chmod (Readme) failed: chmod: invalid mode 'False'

Basic template "{{ inventory_hostname }}" creates the file

root@testwrt:~# cat Readme 
testwrt.test.lan

but reports the error

TASK [Make Readme for testwrt.test.lan] ****************************************
fatal: [testwrt.test.lan]: FAILED! => {"changed": false, "checksum": "adc83b19e793491b1c6ea0fd8b46cd9f32e592fc", "msg": "chmod (Readme) failed: chmod: invalid mode 'False'", "path": "Readme", "state": "file"}
        to retry, use: --limit @/home/user/ansible-bug/routers.retry

PLAY RECAP *********************************************************************
testwrt.test.lan           : ok=11   changed=0    unreachable=0    failed=1

and stops the playbook.
I'm quite new at Ansible. No idea how to debug the shell script on remote router.
...
--------- To Replay the Bug Copy and Paste ---------

WD=`pwd`/ansible-bug
mkdir $WD

# Install ansible in virtualenv
cd $WD
sudo pip3 install -U virtualenv
virtualenv .venv
. .venv/bin/activate
pip install ansible

# Clone gekmihesg/ansible
mkdir roles; cd roles/
git clone  https://github.com/gekmihesg/ansible-openwrt.git
mv ansible-openwrt/ openwrt/

# Configure bug demo
cd $WD
mkdir -p roles/openwrt/vars/
cat << EOF > roles/openwrt/vars/main.yml 
#// Reduce flash wear on target device
ansible_remote_tmp: /tmp/ansible
ansible_ssh_transfer_method: scp
EOF
cat << EOF > hosts
[openwrt]
testwrt.test.lan
EOF
cat << EOF > routers.yml
- hosts: openwrt
  roles:
    - openwrt
  gather_facts: no
  tasks: 
  - name: Make Readme for {{inventory_hostname}}
    template:
      src: roles/openwrt/templates/Readme.j2
      dest: Readme
      mode: '0644'
EOF
mkdir -p roles/openwrt/templates
cat << EOF > roles/openwrt/templates/Readme.j2
{{ inventory_hostname }}
EOF

ansible-playbook -v -i hosts routers.yml -l testwrt.test.lan 2>&1 | tee .buglog

Ok, AFTER loosing a day I stumbled across temporary solution shown as explicit "mode: '0644' " parameter above. Strange, as it requires both single quote and leading zero. According to Ansible documentation one should suffice, and should work without specifying the mode as well, as it actually works in other non-openwrt roles.

How to dump facts using the setup module

I'd like to dump facts from an openwrt (19.07.8) device, but the setup module complains about a missing python interpreter:

❯ ansible -m setup --tree facts box
box | FAILED! => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "module_stderr": "Shared connection to 192.168.8.1 closed.\r\n",
    "module_stdout": "/bin/sh: /usr/bin/python: not found\r\n",
    "msg": "The module failed to execute correctly, you probably need to set the interpreter.\nSee stdout/stderr for the exact error",
    "rc": 127
}

I like that your role doesn't need python installed, but it there any way how I can achieve this ?

Ansible 2.13.5, latest master for ansible-openwrt as of today.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.