Any ansible users / gurus out there

See other thread - from @Rosika about incron…

Decided to take another look at Ansible and it “seemed” to work on one file… but then stopped working… the playbook runs through, but the files with perms “not” 0600 and still “not” 0600 :

---
- name: file module demo
  hosts: all
  vars:
    myfile1: "~user/path/.*"
    myfile2: "~user/path/*"
    myfile3: "~user/path/*.expect"
  become: false
  tasks:
    - name: check permission
      ansible.builtin.file:
        path: "{{ myfile1 }}"
        mode: '0600'
      ansible.builtin.file:
        path: "{{ myfile2 }}"
        mode: '0600'
      ansible.builtin.file:
        path: "{{ myfile3 }}"
        mode: '0700'

Note : I’ve allowed for BOTH " * " and " . " as shell tools are NEVER consistent as to whether " * " also covers “dot files”.

But - e.g. after I run this (playbook), file “g.hosts” which was still 0644, when it should be 0600 :

fenriz (Fedora 35) :
-rw-r--r--. 1 x x 1008 Mar 28 16:24 g.hosts

beere253 (Raspbian buster Pi4) :
-rw-r--r-- 1 x x 1008 Mar 28 16:24 g.hosts

titan (Ubuntu 20.04 desktop) :
-rw-rw-r-- 1 x x 1008 Mar 28 16:24 g.hosts

But it IS working on “expect.expect” :

fenriz (777) BEFORE :
-rwxrwxrwx 1 x x 1096 Mar 28 12:34 expecto.expect

fenriz (700) AFTER (running playbook) :
-rwx------ 1 x x 1096 Mar 28 12:34 expecto.expect

Any ideas ???

Note the perm change for expecto.expect even works on my Macbook! But only seems to be enforcing on that file only…

Do I need to break those tasks up into separate tasks?

Separate playbooks???


Hmmm - do I need to cover ALL these scenarios ?

*.*
*
.*

Another update - tried splitting that up further in the playbook :

---
- name: file module demo
  hosts: all
  vars:
    myfile1: "path/*"
    myfile2: "path/expecto.expect"
  become: false
  tasks:
    - name: check permission
      ansible.builtin.file:
        path: "{{ myfile1 }}"
        #         state: directory
        mode: '0600'

    - name: check permission
      ansible.builtin.file:
        path: "{{ myfile2 }}"
        # state: file
        mode: '0700'

the task works for the file expecto.expect, but I get red errors (on output from running playbook) for the wild card entry :

fatal: [fenriz]: FAILED! => {"changed": false, "msg": "file (path/*) is absent, cannot continue", "path": "path/*"}
fatal: [bebhionn]: FAILED! => {"changed": false, "msg": "file (path/*) is absent, cannot continue", "path": "path/*"}
fatal: [beere253]: FAILED! => {"changed": false, "msg": "file (path/*) is absent, cannot continue", "path": "path/*"}
fatal: [titan]: FAILED! => {"changed": false, "msg": "file (path/*) is absent, cannot continue", "path": "path/*"}
fatal: [rhel8000]: FAILED! => {"changed": false, "msg": "file (path/*) is absent, cannot continue", "path": "path/*"}
fatal: [telesto]: FAILED! => {"changed": false, "msg": "file (path/*) is absent, cannot continue", "path": "path/*"}
fatal: [phoebe]: FAILED! => {"changed": false, "msg": "file (path/*) is absent, cannot continue", "path": "path/*"}
fatal: [orenmwy]: FAILED! => {"changed": false, "msg": "file (path/*) is absent, cannot continue", "path": "path/*"}

Note : I’ve omitted FreeBSD and MacOs from that output 'cause I couldn’t be arsed redacting those paths (they slightly different from Linux - but ~ globs just fine.

It would always work on the second file, because anyone is allowed to do anything (777 a.k.a. rwxrwxrwx).
The first file is more restricting.

My first guess would be to check, if, whoever is running Ansible (OS user), is also the same user owning the files. I doubt that is the case in this situation.

Some of the following sources seem to suggest, it should not be needed. However, I’m not sure.

https://docs.ansible.com/ansible/latest/collections/ansible/builtin/fileglob_lookup.html
https://fabianlee.org/2021/07/27/ansible-find-module-to-create-glob-of-remote-files/

https://fabianlee.org/2021/03/24/ansible-generating-content-for-all-template-files-using-with_fileglob/

This sounds, as if it tried to take this name literally, instead of expanding the glob expression.

I should start playing with some “other” folder than this one - that way I can have the real/actual paths to paste as the output instead of redacting them for “publication” here… makes more sense… this isn’t for work - but knowing ansible, for work, will be useful… I’ve used it in the past, but, only running pretty much “done” playbooks, that someone else wrote, and I’d just edit or tweak a value here and there…

Because “path/*” does exist…

I just tried it again on a folder where I’d DELIBERATELY changed EVERYTHING to 777 and it ONLY worked on expecto.expect - NONE of the other files… expecto.expect is owned by the same UID as ALL the other files in that folder, and that folder is owned by the same UID as all the other files, and that UID (me) is running the ansible playbook… Ansible is using my stored SSH keys… all that’s working…

I suspect it’s something to do with globbing of wildcards anyway…

This is PURELY a learning exercise - my shell script works 100%… I don’t NEED ansible to do this…

Doing more troubleshooting now :

Macbook Pro :

╭─x@methone.local ~/ResilioSync/motorforker/SHONKO
╰─➤  pwd                                                                                                        127 ↵
/Users/x/ResilioSync/motorforker/SHONKO
╭─x@methone.local ~/ResilioSync/motorforker/SHONKO
╰─➤  ls -al
total 0
drwxr-xr-x   7 x  staff   224 29 Mar 08:49 .
drwxr-xr-x@ 34 x  staff  1088 29 Mar 08:48 ..
-rw-r--r--   1 x  staff     0 29 Mar 08:49 .puncho
-rw-r--r--   1 x  staff     0 29 Mar 08:49 .vulgar
-rw-r--r--   1 x  staff     0 29 Mar 08:48 bogus
-rw-r--r--   1 x  staff     0 29 Mar 08:48 shitshow
-rwx------   1 x  staff     0 29 Mar 08:49 shonko.expect
╭─x@methone.local ~/ResilioSync/motorforker/SHONKO
╰─➤  chmod 644 shonko.expect
╭─x@methone.local ~/ResilioSync/motorforker/SHONKO
╰─➤  ls -al
total 0
drwxr-xr-x   7 x  staff   224 29 Mar 08:49 .
drwxr-xr-x@ 34 x  staff  1088 29 Mar 08:48 ..
-rw-r--r--   1 x  staff     0 29 Mar 08:49 .puncho
-rw-r--r--   1 x  staff     0 29 Mar 08:49 .vulgar
-rw-r--r--   1 x  staff     0 29 Mar 08:48 bogus
-rw-r--r--   1 x  staff     0 29 Mar 08:48 shitshow
-rw-r--r--   1 x  staff     0 29 Mar 08:49 shonko.expect

Brix running RHEL 8 :

╭─x@phoebe.local ~/ResilioSync/motorforker/SHONKO  
╰─➤  ls -al                                                                                                                                      2 ↵
total 4
drwxr-xr-x.  2 x x   86 Mar 29 08:49 .
drwxrwxr-x. 23 x x 4096 Mar 29 08:48 ..
-rw-r--r--.  1 x x    0 Mar 29 08:48 bogus
-rw-r--r--.  1 x x    0 Mar 29 08:49 .puncho
-rw-r--r--.  1 x x    0 Mar 29 08:48 shitshow
-rwx------.  1 x x    0 Mar 29 08:49 shonko.expect
-rw-r--r--.  1 x x    0 Mar 29 08:49 .vulgar

** Ansible playbook** :

╭─x@titan ~/ResilioSync/motorforker/ansy  
╰─➤  cat file3.yml 
---
- name: file module demo
  hosts: all
  gather_facts: no
  vars:
    mydiry: "~x/ResilioSync/motorforker/SHONKO/*"
    myfile: "~x/ResilioSync/motorforker/SHONKO/shonko.expect"
  become: false
  tasks:
          #    - name: check permission
          #      ansible.builtin.file:
          #        path: "{{ mydiry }}"
          #        #         state: directory
          #        mode: '0600'
    - name: check permission
      ansible.builtin.file:
        path: "{{ myfile }}"
        # state: file
        mode: '0700'

Kick it off :

╭─x@titan ~/ResilioSync/motorforker/ansy  
╰─➤  ansible-playbook file3.yml 
 _________________________
< PLAY [file module demo] >
 -------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

 _________________________
< TASK [check permission] >
 -------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

ok: [titan]
[WARNING]: Platform linux on host beere253 is using the discovered Python interpreter at /usr/bin/python, but future installation of another Python interpreter could change this. See
https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more information.
ok: [beere253]
[WARNING]: Platform linux on host telesto is using the discovered Python interpreter at /usr/bin/python, but future installation of another Python interpreter could change this. See
https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more information.
ok: [telesto]
ok: [bebhionn]
ok: [phoebe]
ok: [rhel8000]
ok: [orenmwy]
[WARNING]: Platform freebsd on host baphomet is using the discovered Python interpreter at /usr/local/bin/python3.7, but future installation of another Python interpreter could change this. See
https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more information.
ok: [baphomet]
[WARNING]: Platform darwin on host methone is using the discovered Python interpreter at /usr/bin/python, but future installation of another Python interpreter could change this. See
https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more information.
changed: [methone]
ok: [fenriz]
 ____________
< PLAY RECAP >
 ------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

baphomet                   : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
bebhionn                   : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
beere253                   : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
fenriz                     : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
methone                    : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
orenmwy                    : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
phoebe                     : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
rhel8000                   : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
telesto                    : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
titan                      : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Macbook Pro:

╭─x@methone.local ~/ResilioSync/motorforker/SHONKO
╰─➤  ls -al
total 0
drwxr-xr-x   7 x  staff   224 29 Mar 08:49 .
drwxr-xr-x@ 34 x  staff  1088 29 Mar 08:48 ..
-rw-r--r--   1 x  staff     0 29 Mar 08:49 .puncho
-rw-r--r--   1 x  staff     0 29 Mar 08:49 .vulgar
-rw-r--r--   1 x  staff     0 29 Mar 08:48 bogus
-rw-r--r--   1 x  staff     0 29 Mar 08:48 shitshow
-rwx------   1 x  staff     0 29 Mar 08:49 shonko.expect

So, it works on a SPECIFIC file (shonko.expect) - but - NOT when I try to do it using WILDCARDS (wildcard section commented out of playbook)! There are ways of doing thing using SHELL or COMMAND stuff - but - THAT’S NOT the ansible way - I should be using the API and YAML Ansible code, not escaping out to a shell (otherwise I might has well just running my SHELL scripts!

So - here - I uncomment that extra “wildcard” code from my file3.yml playbook :

Ansible playbook :

---
- name: file module demo
  hosts: all
  gather_facts: no
  vars:
    mydiry: "~x/ResilioSync/motorforker/SHONKO/*"
    myfile: "~x/ResilioSync/motorforker/SHONKO/shonko.expect"
  become: false
  tasks:
    - name: check permission
      ansible.builtin.file:
        path: "{{ mydiry }}"
        #         state: directory
        mode: '0600'
    - name: check permission
      ansible.builtin.file:
        path: "{{ myfile }}"
        # state: file
        mode: '0700'

Then over on the Macbook :

╭─x@methone.local ~/ResilioSync/motorforker/SHONKO
╰─➤  chmod 644 shonko.expect
╭─x@methone.local ~/ResilioSync/motorforker/SHONKO
╰─➤  ls -al
total 0
drwxr-xr-x   7 x  staff   224 29 Mar 08:49 .
drwxr-xr-x@ 34 x  staff  1088 29 Mar 08:48 ..
-rw-r--r--   1 x  staff     0 29 Mar 08:49 .puncho
-rw-r--r--   1 x  staff     0 29 Mar 08:49 .vulgar
-rw-r--r--   1 x  staff     0 29 Mar 08:48 bogus
-rw-r--r--   1 x  staff     0 29 Mar 08:48 shitshow
-rw-r--r--   1 x  staff     0 29 Mar 08:49 shonko.expect

Run that sucker :

╭─x@titan ~/ResilioSync/motorforker/ansy  
╰─➤  ansible-playbook file3.yml
 _________________________
< PLAY [file module demo] >
 -------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

 _________________________
< TASK [check permission] >
 -------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

fatal: [titan]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "msg": "file (/home/x/ResilioSync/motorforker/SHONKO/*) is absent, cannot continue", "path": "/home/x/ResilioSync/motorforker/SHONKO/*"}
[WARNING]: Platform linux on host beere253 is using the discovered Python interpreter at /usr/bin/python, but future installation of another Python interpreter could change this. See
https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more information.
fatal: [beere253]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "msg": "file (/home/x/ResilioSync/motorforker/SHONKO/*) is absent, cannot continue", "path": "/home/x/ResilioSync/motorforker/SHONKO/*"}
[WARNING]: Platform linux on host telesto is using the discovered Python interpreter at /usr/bin/python, but future installation of another Python interpreter could change this. See
https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more information.
fatal: [telesto]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "msg": "file (/home/x/ResilioSync/motorforker/SHONKO/*) is absent, cannot continue", "path": "/home/x/ResilioSync/motorforker/SHONKO/*"}
fatal: [bebhionn]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "msg": "file (/home/x/ResilioSync/motorforker/SHONKO/*) is absent, cannot continue", "path": "/home/x/ResilioSync/motorforker/SHONKO/*"}
fatal: [fenriz]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "msg": "file (/home/x/ResilioSync/motorforker/SHONKO/*) is absent, cannot continue", "path": "/home/x/ResilioSync/motorforker/SHONKO/*"}
fatal: [phoebe]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"}, "changed": false, "msg": "file (/home/x/ResilioSync/motorforker/SHONKO/*) is absent, cannot continue", "path": "/home/x/ResilioSync/motorforker/SHONKO/*"}
fatal: [rhel8000]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"}, "changed": false, "msg": "file (/home/x/ResilioSync/motorforker/SHONKO/*) is absent, cannot continue", "path": "/home/x/ResilioSync/motorforker/SHONKO/*"}
fatal: [orenmwy]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "msg": "file (/home/x/ResilioSync/motorforker/SHONKO/*) is absent, cannot continue", "path": "/home/x/ResilioSync/motorforker/SHONKO/*"}
[WARNING]: Platform freebsd on host baphomet is using the discovered Python interpreter at /usr/local/bin/python3.7, but future installation of another Python interpreter could change this. See
https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more information.
fatal: [baphomet]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/local/bin/python3.7"}, "changed": false, "msg": "file (/mnt/BARGEARSE/home/x/ResilioSync/motorforker/SHONKO/*) is absent, cannot continue", "path": "/mnt/BARGEARSE/home/x/ResilioSync/motorforker/SHONKO/*"}
[WARNING]: Platform darwin on host methone is using the discovered Python interpreter at /usr/bin/python, but future installation of another Python interpreter could change this. See
https://docs.ansible.com/ansible/2.9/reference_appendices/interpreter_discovery.html for more information.
fatal: [methone]: FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "msg": "file (/Users/x/ResilioSync/motorforker/SHONKO/*) is absent, cannot continue", "path": "/Users/x/ResilioSync/motorforker/SHONKO/*"}
 ____________
< PLAY RECAP >
 ------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

baphomet                   : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   
bebhionn                   : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   
beere253                   : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   
fenriz                     : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   
methone                    : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   
orenmwy                    : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   
phoebe                     : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   
rhel8000                   : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   
telesto                    : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   
titan                      : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

The Macbook AFTER running that shonky code :

╭─x@methone.local ~/ResilioSync/motorforker/SHONKO
╰─➤  ls -al
total 0
drwxr-xr-x   7 x  staff   224 29 Mar 08:49 .
drwxr-xr-x@ 34 x  staff  1088 29 Mar 08:48 ..
-rw-r--r--   1 x  staff     0 29 Mar 08:49 .puncho
-rw-r--r--   1 x  staff     0 29 Mar 08:49 .vulgar
-rw-r--r--   1 x  staff     0 29 Mar 08:48 bogus
-rw-r--r--   1 x  staff     0 29 Mar 08:48 shitshow
-rw-r--r--   1 x  staff     0 29 Mar 08:49 shonko.expect

So - it’s broken… somehow or other - I was able to get it to do the expect script, but none of the others, now when it barfs, it seems to barf on everything, and does NOTHING… My issue is obviously wildcards - but when I search for “wildcard” and “ansible.builtin.file” - I get NO help! I get plenty of generic samples about using that module, but NO mention of wildcards, or globbing or whatever - however - GLOBBING is working on things like tilde “~” for home directory of user…


Tried using some other ansible code - and it just barfs completely and never tries to run at all (stole it from here : How to use wild card while giving file permission using Ansible? - Stack Overflow) :
Playbook (file3.yml) :

---
- name: file module demo
  hosts: all
  gather_facts: no
  vars:
    mydiry: "~x/ResilioSync/motorforker/SHONKO/*"
    myfile: "~x/ResilioSync/motorforker/SHONKO/shonko.expect"
  become: false
  tasks:
          #    - name: check permission
          #      ansible.builtin.file:
          #        path: "{{ mydiry }}"
          #        #         state: directory
          #        mode: '0600'
    - name: check permission
      ansible.builtin.file:
        path: "{{ myfile }}"
        # state: file
        mode: '0700'
    - file:
      path: '{{ mydiry }}'
      #      owner: tomcat
      #      group: tomcat
      mode: 0600
    with_fileglob:
      - "~x/ResilioSync/motorforker/SHONKO/*"

Run playbook :

╭─x@titan ~/ResilioSync/motorforker/ansy  
╰─➤  ansible-playbook file3.yml
ERROR! Syntax Error while loading YAML.
  did not find expected '-' indicator

The error appears to be in '/home/x/ResilioSync/motorforker/ansy/file3.yml': line 25, column 5, but may
be elsewhere in the file depending on the exact syntax problem.

The offending line appears to be:

      mode: 0600
    with_fileglob:
    ^ here

I guess that serves me right for trying shit code from stack overflow… I know the issue is probably indentation or something - but I can’t be arsed (I did actually try) debugging that…
I know one fix: but that’s the wrong answer - anticipate EVERY single file and explicitly specify that file ! Lotsa (too much) administrative overhead…

I just need to figure out how to get that SHITTY “ansible.builtin.file” module to accept wildcards… :angry:


update - made some progress - 2 steps forward, 1 step back…

dir.yml : 
╭─x@titan ~/ResilioSync/motorforker/ansy  
╰─➤  cat dir.yml 
---
- name: testicles
  hosts: arm_debian, x86_64_ubuntu, x86_64_RHEL
  gather_facts: no
  vars:
    dirdir : "~x/ResilioSync/motorforker/SHONKO"
  tasks:
    - name: tweak permissions
      file:
        dest: "{{ item }}"
        mode: 0600
      with_fileglob:
        - '{{ dirdir }}/*'
        - '{{ dirdir }}/.*'
    - name: tweak exec permissions
      file:
        dest: "{{ item }}"
        mode: 0700
      with_fileglob:
        - '{{ dirdir }}/*.expect'

The “file” builtin (assume it’s a builtin) is too dumb to know that “~x” means $HOME on FreeBSD and/or MacOS :angry: (yeah - I know @Akito - serves me right for using BSD and MacOS [i.e. great chunks of FreeBSD code on Apple’s MACH kernel]). So - I guess I can rely on ansible to do this job on my Linux boxes, but the shell script solution for FreeBSD and Macintosh?

OK - I’ve figured out what’s kinda / sorta going on…

ansible.builtin.file: translates (globs) “~” to mean $HOME path on the REMOTE instance!

file: translates “~” to mean $HOME path on the ansible host (not the remote host)

So - I just need to figure out how to make file work with “~” on MacOS and FreeBSD… Or not bother with these (But I WANT THIS ON MAC!).


Sorted it on BSD in a separate playbook - explicit path to my home on my FreeNAS sorted that - but - APPLE???
Seriously - this is truly f–ked up! F-CK APPLE! :angry:

╭─x@methone.local ~
╰─➤  pwd
/Users/x
╭─x@methone.local ~
╰─➤  df -h .
Filesystem     Size   Used  Avail Capacity iused      ifree %iused  Mounted on
/dev/disk3s1  228Gi  172Gi   40Gi    82% 1032700 2392592260    0%   /System/Volumes/Data
╭─x@methone.local /System/Volumes/Data/Users/x
╰─➤  cd /System/Volumes/Data/Users/x/ResilioSync/motorforker/SHONKO

mac-dir.yml ansible playbook (excerpt) :

    dirdir: /System/Volumes/Data/Users/x/ResilioSync/motorforker/SHONKO/

Then play that sucker :

╭─x@titan ~/ResilioSync/motorforker/ansy  
╰─➤  ansible-playbook mac-dir.yml
 __________________
< PLAY [testicles] >
 ------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

 __________________________
< TASK [tweak permissions] >
 --------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

[WARNING]: Unable to find '/System/Volumes/Data/Users/x/ResilioSync/motorforker/SHONKO' in expected paths (use -vvvvv to see paths)
 _______________________________
< TASK [tweak exec permissions] >
 -------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

[WARNING]: Unable to find '/System/Volumes/Data/Users/x/ResilioSync/motorforker/SHONKO' in expected paths (use -vvvvv to see paths)
 ____________
< PLAY RECAP >
 ------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

methone                    : ok=0    changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0

Then with debug “-vvvvv” (and disabled cowsay in /etc/ansible/ansible.cfg) :

╭─x@titan ~/ResilioSync/motorforker/ansy  
╰─➤  ansible-playbook -vvvvv mac-dir.yml
ansible-playbook 2.9.6
  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/dist-packages/ansible
  executable location = /usr/bin/ansible-playbook
  python version = 3.8.10 (default, Mar 15 2022, 12:22:08) [GCC 9.4.0]
Using /etc/ansible/ansible.cfg as config file
setting up inventory plugins
host_list declined parsing /etc/ansible/hosts as it did not pass its verify_file() method
script declined parsing /etc/ansible/hosts as it did not pass its verify_file() method
auto declined parsing /etc/ansible/hosts as it did not pass its verify_file() method
Parsed /etc/ansible/hosts inventory source with ini plugin
Loading callback plugin default of type stdout, v2.0 from /usr/lib/python3/dist-packages/ansible/plugins/callback/default.py

PLAYBOOK: mac-dir.yml ********************************************************************************************************************************
Positional arguments: mac-dir.yml
verbosity: 5
connection: smart
timeout: 10
become_method: sudo
tags: ('all',)
inventory: ('/etc/ansible/hosts',)
forks: 5
1 plays in mac-dir.yml

PLAY [testicles] *************************************************************************************************************************************
META: ran handlers

TASK [tweak permissions] *****************************************************************************************************************************
task path: /home/x/ResilioSync/motorforker/ansy/mac-dir.yml:15
[WARNING]: Unable to find '/System/Volumes/Data/Users/x/ResilioSync/motorforker/SHONKO' in expected paths (use -vvvvv to see paths)

TASK [tweak exec permissions] ************************************************************************************************************************
task path: /home/x/ResilioSync/motorforker/ansy/mac-dir.yml:22
[WARNING]: Unable to find '/System/Volumes/Data/Users/x/ResilioSync/motorforker/SHONKO' in expected paths (use -vvvvv to see paths)
META: ran handlers
META: ran handlers

PLAY RECAP *******************************************************************************************************************************************
methone                    : ok=0    changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0

Note : my “old” original playbook, that used “ansible.builtin.file” worked equally well (or badly) on ALL platforms - “~” was ALWAYS globbed to the correct location… But it doesn’t glob F–KING wildcards :angry:


then you get gobshites who write tutorials that are just wrong - it might work “locally” but it won’t work on REMOTE target machines (which is the WHOLE purpose of f–king Ansible in the first place to manage multiple machines :

He uses $HOME in numerous example playbooks - but - it DOES NOT work on remote machines!

My playbook (to run on all of the things - using $HOME like the gobshite in that link does) :

╭─x@titan ~/ResilioSync/motorforker/ansy  
╰─➤  cat mac-dir.yml 
---
- name: testicles
  hosts: all
  # hosts: arm_debian, x86_64_ubuntu, x86_64_RHEL
  # hosts: apple,freebsd
  # hosts: freebsd
  # hosts: apple
  gather_facts: no
  vars:
          # ~x being globbed as local $HOME, not remote $HOME, so it doesn't work on MacOS or FreeNAS
          # dirdir : "~x/ResilioSync/motorforker/SHONKO"
          # dirdir: /Users/x/ResilioSync/motorforker/SHONKO/
          # dirdir: /System/Volumes/Data/Users/x/ResilioSync/motorforker/SHONKO
          dirdir: 'ResilioSync/motorforker/SHONKO'
  tasks:
    - name: tweak permissions
      file:
              # path: $HOME/ResilioSync/motorforker/SHONKO
        dest: "{{ item }}"
        mode: 0600
      with_fileglob:
        - '$HOME/{{ dirdir }}/*'
        - '$HOME/{{ dirdir }}/.*'
    - name: tweak exec permissions
      file:
              # path: $HOME/ResilioSync/motorforker/SHONKO
        dest: "{{ item }}"
        mode: 0700
      with_fileglob:
        - '$HOME/{{ dirdir }}/*.expect'

And result (DOESN’T work anywhere) :

╭─x@titan ~/ResilioSync/motorforker/ansy  
╰─➤  ansible-playbook mac-dir.yml

PLAY [testicles] *****************************************************************************************************************************

TASK [tweak permissions] *********************************************************************************************************************
[WARNING]: Unable to find '$HOME/ResilioSync/motorforker/SHONKO' in expected paths (use -vvvvv to see paths)
[WARNING]: Unable to find '$HOME/ResilioSync/motorforker/SHONKO' in expected paths (use -vvvvv to see paths)
[WARNING]: Unable to find '$HOME/ResilioSync/motorforker/SHONKO' in expected paths (use -vvvvv to see paths)
[WARNING]: Unable to find '$HOME/ResilioSync/motorforker/SHONKO' in expected paths (use -vvvvv to see paths)
[WARNING]: Unable to find '$HOME/ResilioSync/motorforker/SHONKO' in expected paths (use -vvvvv to see paths)
[WARNING]: Unable to find '$HOME/ResilioSync/motorforker/SHONKO' in expected paths (use -vvvvv to see paths)
[WARNING]: Unable to find '$HOME/ResilioSync/motorforker/SHONKO' in expected paths (use -vvvvv to see paths)
[WARNING]: Unable to find '$HOME/ResilioSync/motorforker/SHONKO' in expected paths (use -vvvvv to see paths)
[WARNING]: Unable to find '$HOME/ResilioSync/motorforker/SHONKO' in expected paths (use -vvvvv to see paths)
[WARNING]: Unable to find '$HOME/ResilioSync/motorforker/SHONKO' in expected paths (use -vvvvv to see paths)

TASK [tweak exec permissions] ****************************************************************************************************************
[WARNING]: Unable to find '$HOME/ResilioSync/motorforker/SHONKO' in expected paths (use -vvvvv to see paths)
[WARNING]: Unable to find '$HOME/ResilioSync/motorforker/SHONKO' in expected paths (use -vvvvv to see paths)
[WARNING]: Unable to find '$HOME/ResilioSync/motorforker/SHONKO' in expected paths (use -vvvvv to see paths)
[WARNING]: Unable to find '$HOME/ResilioSync/motorforker/SHONKO' in expected paths (use -vvvvv to see paths)
[WARNING]: Unable to find '$HOME/ResilioSync/motorforker/SHONKO' in expected paths (use -vvvvv to see paths)
[WARNING]: Unable to find '$HOME/ResilioSync/motorforker/SHONKO' in expected paths (use -vvvvv to see paths)
[WARNING]: Unable to find '$HOME/ResilioSync/motorforker/SHONKO' in expected paths (use -vvvvv to see paths)
[WARNING]: Unable to find '$HOME/ResilioSync/motorforker/SHONKO' in expected paths (use -vvvvv to see paths)
[WARNING]: Unable to find '$HOME/ResilioSync/motorforker/SHONKO' in expected paths (use -vvvvv to see paths)
[WARNING]: Unable to find '$HOME/ResilioSync/motorforker/SHONKO' in expected paths (use -vvvvv to see paths)

PLAY RECAP ***********************************************************************************************************************************
baphomet                   : ok=0    changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   
bebhionn                   : ok=0    changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   
beere253                   : ok=0    changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   
fenriz                     : ok=0    changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   
methone                    : ok=0    changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   
orenmwy                    : ok=0    changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   
phoebe                     : ok=0    changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   
rhel8000                   : ok=0    changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   
telesto                    : ok=0    changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   
titan                      : ok=0    changed=0    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0

Maybe not such a gobshite - he’s an Aussie too… hmmm $HOME works intermittently… He gives an example about not using $HOST, but he still explicitly gives the root folder “/home/{{ variable }}/folder”…


Update : shonky, kludge solution…
One playbook for ALL the Linuxes (10)
One playbook for my shell account on my FreeBSD FreeNAS (1)
One shellscript for Apple MacBook Pro M1 :angry:

Until I can figure out how to use wildcard with ansible’s built in “ansible.builtin.file” module!


still not happy - I hate kludges…
But - I now have 4 playbooks…
1 one that does ALL use cases for ALL Linux servers (e.g. RPi and x86_64 etc)
2 one that does my home dir on my FreeNAS
3 Apple : one that sets 0700 on the main folder, and 600 on the files in it
4 Apple : one that sets shonko.expect and crap.expect to 0700

I guess I could merge 3 and 4 into one playbook and hope I don’t break the f–king YAML :

Overkill - not the right answer I know - a kludge… I reckon I was on the right track first off using the “ansible.builtin.file” module, it works EVERYWHER, and it knows that “~” means different things depending where you’re going (i.e. Mac $HOME is NOT in /home - and ansible.builtin.file knows this) - but I CANNOT get ansible.builtin.file to use wildcards on files - and I DO NOT want to go through EVERY possible file I might host in that folder!

And then I guess I could merge ALL 4 into a single playbook ??? Need to figure out how to do that…


tried to merge them all and it crapped itself… I need to get an understanding of how to do playbooks that do multiple shit, procedurally…


This kinda works - but I’m not crazy about it, it quacks like a kludge, it walks like a kludge, methinks its a kludge, and its very inelegant :

cd $PLAYBOOKSDIR
ansible-playbook lnx-dir.yml bsd-dir.yml mac2-dir.yml mac3-dir.yml

Sounds reasonable.

:clap:

:clap:

:clap:

:clap:

:clap:

:laughing:

This is what I hate about the tutorial spam online. There are a trillion tutorials for the most easy shit possible, most of them copying each other or having an original source of truth for the specific topic used for the tutorial.
However, if you want to find anything advanced, in any way, you already have to search and search and even then it’s just one specific example, which probably doesn’t apply to your setup, anyway. Usually, that means, you need to read several tutorials about several topics and patch them together in your head to make them work on your setup. :grin:

It’s really annoying. Well, to be fair, still better than having to go to the library in the 80’s and read a big fat book about how computers work, just to get something to work…

https://docs.ansible.com/ansible/latest/collections/ansible/builtin/file_module.html

What about using the shell module or a classic regex:

https://docs.ansible.com/ansible/latest/collections/ansible/builtin/shell_module.html

Or not using ansible.builtin.file in the first place:
https://docs.ansible.com/ansible/latest/collections/ansible/builtin/find_module.html
https://docs.ansible.com/ansible/latest/collections/ansible/builtin/fetch_module.html

- hosts: all
  tasks:
  - name: Ansible delete file wildcard
    find:
      paths: /etc/wild_card/example
      patterns: "^he.*.txt"
      use:regex: true
    register: wildcard_files_to_delete

  - name: Ansible remove file wildcard
    file:
      path: "{{ item.path }}"
      state: absent
    with_items: "{{ wildcard_files_to_delete.files }}"

As seen above, it seems like Ansible may also use some Go Template kind of crap. Hate that. But maybe it can get your job done in your scenario. When using Helm, you can include files by glob through those Go Templates. Perhaps, it’s also possible in Ansible.

1 Like

There is another way. Go back to basics and work it out for yourself.

That takes forever, though, at least compared to a good & quick tutorial. And the amount of knowledge you would need to study, to gather all the information by yourself, through basics only, would take so much time, you would forget why you even started doing that.

Additionally, a lot of basics are redundant and therefore you would quickly forget about them. So, if you do all the basics, then you waste 80% of study time on basics, you probably won’t need anytime soon, anyway.

1 Like

How much time do misleading tutorials waste?
I use tutorials, but I have trouble finding the good ones - for example in doing that recent post on bootable usb’s I could not find any tutorial that helped. Had to read the 155 page grub manual.

Cheers
Neville

In the beginning, a lot. However, getting to know tutorials is a study on its own. If you get used to consuming online tutorials over the years, then you can quickly distinguish how much parts of a tutorials are worth, etc. Additionally, by then already a chunk of fundamental knowledge has been gathered, which allows for even easier & more proficient processing of tutorials.

I think, all in all, comparing both methods, the tutorial method is most likely still more practical and beneficial.

I wouldn’t consider myself an “expert” on any Linux topic or whatever, but compared to newbies and most intermediates I have a good base knowledge, which helps a lot. Not only me, but others, as well.
To achieve that, I never had to read an entire manual from cover to cover. Never ever.
Never did that and I hope and I will never have to, except maybe for Linux from Scratch, but that’s another story.

I am also sure you would be able to solve your issue without reading entire manuals.

The best type of knowledge gathering, from my experience anyway, is a mix of good and well written tutorials + manuals.
You read & understand all needed tutorials and fill the gaps with the manual, as needed.
This is what usually works for every techie I know, myself included.

1 Like

Did a whole bunch more reading and thought I’d hit a solution (a one size fits all playbook) :

╭─x@titan ~/ResilioSync/motorforker/ansy  
╰─➤  cat FF.yaml 
---
- name: file module demo
  # hosts: all
  # hosts: apple, x86_64_RHEL
  hosts: apple
  gather_facts: no
  vars:
    mydiry1: "~x/ResilioSync/motorforker/SHONKO/"
    myfile1: "~x/ResilioSync/motorforker/SHONKO/shonko.expect"
    myfile2: "~x/ResilioSync/motorforker/SHONKO/crap.expect"
  become: false
  tasks:
    - name: check permission FILE
      ansible.builtin.file:
        path: "{{ mydiry1 }}"
        state: directory
        recurse: yes
        mode: '0700'
    - name: check permission DIR
      ansible.builtin.file:
        path: "{{ mydiry1 }}"
        state: directory
        recurse: yes
        mode: '0600'
    - name: check permission
      ansible.builtin.file:
        path: "{{ myfile1 }}"
        state: file
        mode: '0700'
    - name: check permission
      ansible.builtin.file:
        path: "{{ myfile2 }}"
        state: file
        mode: '0700'

But the “check permission DIR” breaks everything - i.e. it sets the parent folder to 0600 first, and then it can’t do anything else 'cause the folder’s now 0600… I guess I could settle for everything having u+x (0700) but that’s STILL NOT THE ANSWER!

So I thought I’d fixed that but it still barfs :

╭─x@titan ~/ResilioSync/motorforker/ansy  
╰─➤  cat FF.yaml                                                                                                                                                                                                          2 ↵
---
- name: file module demo
  # hosts: all
  # hosts: apple, x86_64_RHEL
  # hosts: apple
  hosts: x86_64_RHEL
  gather_facts: no
  vars:
    mydiry1: "~x/ResilioSync/motorforker/SHONKO"
    myfile1: "~x/ResilioSync/motorforker/SHONKO/shonko.expect"
    myfile2: "~x/ResilioSync/motorforker/SHONKO/crap.expect"
  become: false
  tasks:
    - name: check permission FILE
      ansible.builtin.file:
        path: "{{ mydiry1 }}"
        state: directory
        recurse: yes
        mode: '0600'
    - name: check permission DIR
      ansible.builtin.file:
        path: "{{ mydiry1 }}"
        state: directory
        recurse: no
        mode: '0700'
    - name: check permission
      ansible.builtin.file:
        path: "{{ myfile1 }}"
        state: file
        mode: '0700'
    - name: check permission
      ansible.builtin.file:
        path: "{{ myfile2 }}"
        state: file
        mode: '0700

i.e. I changed the order around, but it seems to me that ansible is :

  1. setting ~/ResilioSync/motorforker/SHONKO/ parent folder to 0600 - and thereafter it can’t traverse to that folder to recursively set the contents to 0600…
  2. folder is 0600 - ansible cannot access it - evertying thing else fails thereafter

I need the parent folder to be 0700, but the files therein to be 0600, except for the expect scripts, which also need 0700…
I think I’m going to have to settle for 0700 for everything, to avoid having 4 playbooks - which is cumbersome, and tiring…
This will kinda/sorta, work for me, but - it WILL NOT work for managing key files, e.g. PEM files - and other SSH stuff in your ~/.ssh/ folder, the files of which NEED to be 0600.

The answer :

╭─x@titan ~/ResilioSync/motorforker/ansy  
╰─➤  cat ZZ.yaml 
---
- name: file module demo
  hosts: all
  gather_facts: no
  vars:
    mydiry1: "~x/ResilioSync/motorforker/SHONKO"
  become: false
  tasks:
    - name: check permission FILE and DIR
      ansible.builtin.file:
        path: "{{ mydiry1 }}"
        state: directory
        recurse: yes
        mode: '0700'

But I’m not happy, I don’t think 0700 on files, that don’t need 700, is the answer, it’s a kludgy workaround, but I’ll use it…

Been google-fu’ing the intert00bs again - can’t find an answer, there are ansible mobiles specifically for managing SSH stuff - they probably/maybe have builtin knowledge that a public / private keypair should be 0600…

e.g. : ansible.posix.authorized_key – Adds or removes an SSH authorized key — Ansible Documentation

But nothing seems to match what I’m seeking…


Just noticed - openssh doesn’t seem to care if my keys are 0700… or other files in ~/.ssh/ either… hmmm…

Did you try the same with destinations not being on any folder doing remote stuff, like ResilioSync?

I’ve experienced permission issues, when operating on files on shares, etc.

There are minimum and maximum permissions for SSH files & folders.

I looked up a script I created years ago, where I set the minimal permissions for all the SSH files & folders.

Snippet

# At this moment, these are the minimum permissions
# for each file and folder listed here.
declare -a SET_PERMS
SET_PERMS=( "chown ${usr}:${usr} -R   /home/${usr}/.ssh"                 \
            "chmod 0700               /home/${usr}/.ssh"                 \
            "chmod 0400               /home/${usr}/.ssh/config"          \
            "chmod 0600               /home/${usr}/.ssh/authorized_keys" \
            "chmod 0644               /home/${usr}/.ssh/id_rsa.pub"      \
            "chmod 0400               /home/${usr}/.ssh/id_rsa"          \
          )
1 Like

ResilioSync claim that posix file permissions / attributes is trickier than NTFS.

I call bullshit… I call “too lazy to figure it out”…

If Dropbox can do it - then ResilioSync should be able to (RSL is a fork of the OSS “btsync” - it can be used for free - in fact, even though I paid it - how I’m using it - I could do for free anyway).

e.g.
I edit file file1.txt, on my main system. I save it. It gets saved with the same permssions it already had, e.g. 0700. But - when RSL sees that it’s changed, and pushes it out to all the other sync hosts/targets - those machines, if they’re using a POSIX compliant filesystem (i.e. even MacOS), end up with 755… The file(s) are always UID:GID of the user running RSL (and that’s how I want it). I just with RSL was smart enough to read those permissions, it’s so basic, rsync, cp (usually, but not always), scp, tar, zip, dropbox, blah blah - ALL know what POSIX permissions are, and transfer them with the files when the files are shipped elsewhere - but RSL can’t do that? It stinks of laziness to me…

I think a ResilioSync solution to my problem is to have a SINGLE shared folder for that specific folder, and a separate RSL process running that has a different UMASK - but - that’s overkill… I don’t edit these files very often, so a shell script, or ansible is the best answer, for me…

What scripting language was that code snippet done in?

I’m so lazy - I wrote a shitty bash script to run my ansible playbook (warning: lotsa cuss words) :
SPOILER - SWEARING IN HERE :

Summary
╭─x@titan ~  
╰─➤  cat ~/bin/enforcer.bash 
#!/usr/bin/env	bash
# run some ansible playbook fuckers
# export IFS=$'\n'
# DIR=~/ResilioSync/motorforker/ansy
# new DIR was cloned from github : 
DIR=~/ResilioSync/motorforker/ants-sybil
TORTRUE="lnx-dir.yml bsd-dir.yml mac2-dir.yml mac3-dir.yml"
SIBLE=/usr/bin/ansible-playbook

looperfuck () {
for PBOOK in $TORTRUE
do
 	echo "$SIBLE $DIR/$PBOOK"
	$SIBLE $DIR/$PBOOK
done
}

singlefuckgiven () {
	PBOOK=$DIR/ZZ.yaml
	$SIBLE $PBOOK
}

singlefuckgiven
# looperfuck

The second version - single playbook, without a loop, takes 30 seconds, the loop version (with 4 playbooks)? About 3-5 minutes!

One of the things I love about Ansible - is the derivation of the name - it’s from a science fiction “device” (or tech) that Ursula K. Le Guin “invented” for instant (i.e. faster than light) communication (not travel) - mentioned in two of her greatest novels “The Dispossessed” and “The Left Hand of Darkness”. Another science fiction author, Neal Asher, “invented” something called a “runsible” and I assume in deference to Ms Le Guin, which I think allows travel between wormholes or something.

OK…

Hit more snags using ansible… Was planning on doing a udemy course on Ansible (I have SHIT ALL self discipline [and probably undiagnosed ADHD] so I may not get any value from) - as my new employer grant us FULL access to Udemy *… But I don’t want to do the basics crap for n00bs, prefer to start off with an advance course… so anyway…

My octal permissions playbook was working up till quite recently - but I hadn’t run it for a couple of weeks…

It barfed on my “new” (shipped to me by my employer) 2018 MacBook Air 13" (intel i5) - and - my RPi4 running Ubuntu 21.04 (and then I do-release-upgrade’d it to 22.04 and it still FAILED).

In both cases it was seemingly failing on the python version, but, that was a RED f–king HERRING!

On the Intel Mac, it was failing 'cause I needed to install xcode-select (I’d already done that nearly 12 months ago when I first got my M1 MacBook Pro)… once xcode was installed, the ansible playbook ran successfully… initially giving an error about “xcrun” (googled : “ansible missing xcrun” and got my answer).

On the Pi4, it was failing 'cause for whatever bizarre or obtuse reason, ansible wasn’t happy about setting an octal permission on a symlink (yeah - they ALWAYS inherit, but it shouldn’t barf on this) - soon as I removed the symlink - the playbook runs… Doh! But why ONLY on Ubuntu 22.04 (and 21.04) running on a Pi4???

Booting up my 22.04 x86_64 gnome boxes guest to see if it happens there too…


Well a pox on that for an idea! Need to break out some cli fu to do bridged networking in Gnome Boxes. The whole point of Gnome Boxes is to make virtualization a no-brainer! My 22.04 can talk “out” of itself to my LAN and the intert00bs, but my LAN cannot talk back at it, from LAN host initiated packets, which Ansible needs to do… I’ll leave it at that - do NOT use symlinks … not the answer I’d like, and bullshit really…


come back after about 30-45 minutes - installed oracle virtualbox on my fedora desktop, installed a ubuntu 22.04 server (headless) VM… made a copy of that folder… updated playbook, it runs fine… create a symlink on there, playbook runs fine…

Go back to the Ubuntu 22.04 on RPi4 and create symlink and run the playbook - it shits its pants on the symlink - what a piece of crap! Remove the symlink and it’s fine…


* they have a whole bunch of Blender 3D courses I can access too! I might try some of those in my own time!
In 2015 a paid a dude $150 a pop, for three 1 hour sessions, using Blender (I’ve bought books before too) - and it really helped - but - I STUPIDLY didn’t persist and keep practising! Doh!