208 Create iocage template for ansible-pull

TODO: Run ansible-pull on boot.

Use case

Create iocage template ansible_client_pull that will use ansible-pull.

Tree

shell> tree .
.
├── ansible.cfg
├── files
│   └── pk_admins.txt
├── host_vars
│   └── iocage_04
│       └── iocage.yml
└── iocage.ini

Synopsis

  • At the iocage host iocage_04 in the playbook vbotka.freebsd.pb_iocage_template.yml, use the modules:

    • vbotka.freebsd.iocage to create, start, stop, and convert jail to templates.

    • vbotka.freebsd.iocage exec to create a user and set .ssh ownership.

    • community.general.sysrc to configure /etc/rc.conf

    • ansible.posix.authorized_key to configure public keys.

    • ansible.builtin.lineinfile to configure /usr/local/etc/sudoers

    • configure dhclient hooks

Requirements

Notes

TBD

ansible.cfg

[defaults]
gathering = explicit
callback_result_format = yaml
display_skipped_hosts = false

[connection]
pipelining = true

Inventory iocage.ini

iocage_04 ansible_host=10.1.0.29

[iocage]
iocage_04

[iocage:vars]
ansible_user=admin
ansible_become=true
ansible_python_interpreter=auto_silent

host_vars

host_vars/iocage_04/iocage.yml
freebsd_iocage_pool: iocage
freebsd_iocage_pool_mount: /iocage
freebsd_iocage_mount: /iocage/iocage

templates:
  ansible_client_pull:
    release: 15.0-RELEASE
    properties:
      bpf: 1
      dhcp: 1
      vnet: 1
    dhclient: "{{ act_dhclient | dict2items }}"
    rcconf: "{{ act_rcconf | dict2items }}"
    pkglist: /tmp/ansible/ansible_client_pull/pkgs.json

act_user: admin
act_pk: pk_admins.txt
act_sudo: true
act_rcconf:
  iocage_enable: "YES"
  sshd_enable: "YES"
act_dhclient:
  dhclient-exit-hooks: |
    case "$reason" in
        "BOUND"|"REBIND"|"REBOOT"|"RENEW")
        echo $new_ip_address > /var/db/dhclient-hook.address.$interface
        ;;
    esac

Note

The variables act_* are used to configure the template

  • The user act_user will be created in the template.

  • The user act_user will serve as Ansible remote_user

  • The file act_pk provides the public keys allowed to ssh to act_user

  • The dhclient hooks act_dhclient will be created in /etc

Create the file files/pkgs.json

{
    "pkgs": [
        "git",
        "python311",
        "py311-ansible",
        "sudo"
        ]
}

Warning

  • The user act_user must exist on the iocage host. Otherwise, the module ansible.posix.authorized_key will crash. See playbooks/pb_iocage_template/pk.yml

  • The file files/pk_admins.txt was sanitized. Fit the public keys to your needs

    shell> cat files/pk_admins.txt
    ssh-rsa <sanitized> admin@controller
    

Playbook output - Create templates

Limit the inventory to iocage_04

(env) > ansible-playbook vbotka.freebsd.pb_iocage_template.yml \
                         -i iocage.ini -l iocage_04 \
                         -e debug=true
PLAY [Create Ansible client templates.] ****************************************

TASK [Setup: Get iocage list of templates.] ************************************
ok: [iocage_04]

TASK [Setup: Set dictionary iocage_templates] **********************************
ok: [iocage_04]

TASK [Setup: Display iocage_templates debug=true] ******************************
ok: [iocage_04] => 
    iocage_templates:
        ansible_client:
            basejail: 'no'
            boot: 'off'
            ip4: '-'
            ip4_dict:
                ip4: []
                msg: DHCP (not running)
            ip6: '-'
            jid: None
            release: 15.0-RELEASE-p3
            state: down
            template: '-'
            type: template
        ansible_client_apache:
            basejail: 'no'
            boot: 'off'
            ip4: '-'
            ip4_dict:
                ip4: []
                msg: DHCP (not running)
            ip6: '-'
            jid: None
            release: 15.0-RELEASE-p3
            state: down
            template: '-'
            type: template

TASK [Pkglist: Create directories for pkglist files.] **************************
ok: [iocage_04] => (item=ansible_client_pull /tmp/ansible/ansible_client_pull/pkgs.json)

TASK [Pkglist: Copy pkglist files.] ********************************************
ok: [iocage_04] => (item=ansible_client_pull /tmp/ansible/ansible_client_pull/pkgs.json)

TASK [Create: Create templates.] ***********************************************
changed: [iocage_04] => (item=ansible_client_pull 15.0-RELEASE)

TASK [Start: Display iocage_jails debug=true] **********************************
ok: [iocage_04] => 
    iocage_jails:
        ansible_client_pull:
            basejail: 'no'
            boot: 'off'
            ip4: DHCP (not running)
            ip6: '-'
            jid: None
            name: ansible_client_pull
            properties:
                CONFIG_VERSION: '33'
                allow_chflags: '0'
                allow_mlock: '0'
                allow_mount: '0'
                allow_mount_devfs: '0'
                allow_mount_fdescfs: '0'
                allow_mount_fusefs: '0'
                allow_mount_linprocfs: '0'
                allow_mount_linsysfs: '0'
                allow_mount_nullfs: '0'
                allow_mount_procfs: '0'
                allow_mount_tmpfs: '0'
                allow_mount_zfs: '0'
                allow_nfsd: '0'
                allow_quotas: '0'
                allow_raw_sockets: '0'
                allow_set_hostname: '1'
                allow_socket_af: '0'
                allow_sysvipc: '0'
                allow_tun: '0'
                allow_vmm: '0'
                assign_localhost: '0'
                available: readonly
                basejail: '0'
                boot: '0'
                bpf: '1'
                children_max: '0'
                cloned_release: 15.0-RELEASE
                comment: none
                compression: 'on'
                compressratio: readonly
                coredumpsize: 'off'
                count: '1'
                cpuset: 'off'
                cputime: 'off'
                datasize: 'off'
                dedup: 'off'
                defaultrouter: auto
                defaultrouter6: auto
                depends: none
                devfs_ruleset: '4'
                dhcp: '1'
                enforce_statfs: '2'
                exec_clean: '1'
                exec_created: /usr/bin/true
                exec_fib: '0'
                exec_jail_user: root
                exec_poststart: /usr/bin/true
                exec_poststop: /usr/bin/true
                exec_prestart: /usr/bin/true
                exec_prestop: /usr/bin/true
                exec_start: /bin/sh /etc/rc
                exec_stop: /bin/sh /etc/rc.shutdown
                exec_system_jail_user: '0'
                exec_system_user: root
                exec_timeout: '60'
                host_domainname: none
                host_hostname: ansible-client-pull
                host_hostuuid: ansible_client_pull
                host_time: '1'
                hostid: d6add9ad-8b6e-4316-a226-345a60532b41
                hostid_strict_check: '0'
                interfaces: vnet0:bridge0
                ip4: new
                ip4_addr: none
                ip4_saddrsel: '1'
                ip6: new
                ip6_addr: none
                ip6_saddrsel: '1'
                ip_hostname: '0'
                jail_zfs: '0'
                jail_zfs_dataset: iocage/jails/ansible_client_pull/data
                jail_zfs_mountpoint: none
                last_started: '2026-02-24 22:35:58'
                localhost_ip: none
                login_flags: -f root
                mac_prefix: 5a9cfc
                maxproc: 'off'
                memorylocked: 'off'
                memoryuse: 'off'
                min_dyn_devfs_ruleset: '1000'
                mount_devfs: '1'
                mount_fdescfs: '1'
                mount_linprocfs: '0'
                mount_procfs: '0'
                mountpoint: readonly
                msgqqueued: 'off'
                msgqsize: 'off'
                nat: '0'
                nat_backend: ipfw
                nat_forwards: none
                nat_interface: none
                nat_prefix: '172.16'
                nmsgq: 'off'
                notes: none
                nsem: 'off'
                nsemop: 'off'
                nshm: 'off'
                nthr: 'off'
                openfiles: 'off'
                origin: readonly
                owner: root
                pcpu: 'off'
                plugin_name: none
                plugin_repository: none
                priority: '99'
                pseudoterminals: 'off'
                quota: none
                readbps: 'off'
                readiops: 'off'
                release: 15.0-RELEASE-p3
                reservation: none
                resolver: /etc/resolv.conf
                rlimits: 'off'
                rtsold: '0'
                securelevel: '2'
                shmsize: 'off'
                stacksize: 'off'
                state: down
                stop_timeout: '30'
                swapuse: 'off'
                sync_state: none
                sync_target: none
                sync_tgt_zpool: none
                sysvmsg: new
                sysvsem: new
                sysvshm: new
                template: '0'
                type: jail
                used: readonly
                vmemoryuse: 'off'
                vnet: '1'
                vnet0_mac: 5a9cfc7e220c 5a9cfc7e220d
                vnet0_mtu: auto
                vnet1_mac: none
                vnet1_mtu: auto
                vnet2_mac: none
                vnet2_mtu: auto
                vnet3_mac: none
                vnet3_mtu: auto
                vnet_default_interface: none
                vnet_default_mtu: '1500'
                vnet_interfaces: none
                wallclock: 'off'
                writebps: 'off'
                writeiops: 'off'
            release: 15.0-RELEASE-p3
            state: down
            template: '-'
            type: jail

TASK [Start: Start jails.] *****************************************************
ok: [iocage_04]

TASK [User: Create user.] ******************************************************
changed: [iocage_04] => (item=ansible_client_pull admin)

TASK [Pk: The keys are valid.] *************************************************
ok: [iocage_04] => 
    changed: false
    msg: All assertions passed

TASK [Pk: Configure public keys.] **********************************************
changed: [iocage_04] => (item=ansible_client_pull admin)

TASK [Pk: Chown .ssh] **********************************************************
ok: [iocage_04] => (item=ansible_client_pull admin)

TASK [Sudo: Configure sudo.] ***************************************************
changed: [iocage_04] => (item=ansible_client_pull admin)

TASK [Dhclient: Configure hooks.] **********************************************
changed: [iocage_04] => (item=ansible_client_pull dhclient-exit-hooks)

TASK [Rcconf: Configure /etc/rc.conf] ******************************************
changed: [iocage_04] => (item=ansible_client_pull iocage_enable YES)
changed: [iocage_04] => (item=ansible_client_pull sshd_enable YES)

TASK [Stop: Display iocage_jails debug=true] ***********************************
ok: [iocage_04] => 
    iocage_jails:
        ansible_client_pull:
            basejail: 'no'
            boot: 'off'
            ip4: epair0b|10.1.0.212
            ip6: '-'
            jid: '75'
            name: ansible_client_pull
            properties:
                CONFIG_VERSION: '33'
                allow_chflags: '0'
                allow_mlock: '0'
                allow_mount: '0'
                allow_mount_devfs: '0'
                allow_mount_fdescfs: '0'
                allow_mount_fusefs: '0'
                allow_mount_linprocfs: '0'
                allow_mount_linsysfs: '0'
                allow_mount_nullfs: '0'
                allow_mount_procfs: '0'
                allow_mount_tmpfs: '0'
                allow_mount_zfs: '0'
                allow_nfsd: '0'
                allow_quotas: '0'
                allow_raw_sockets: '0'
                allow_set_hostname: '1'
                allow_socket_af: '0'
                allow_sysvipc: '0'
                allow_tun: '0'
                allow_vmm: '0'
                assign_localhost: '0'
                available: readonly
                basejail: '0'
                boot: '0'
                bpf: '1'
                children_max: '0'
                cloned_release: 15.0-RELEASE
                comment: none
                compression: 'on'
                compressratio: readonly
                coredumpsize: 'off'
                count: '1'
                cpuset: 'off'
                cputime: 'off'
                datasize: 'off'
                dedup: 'off'
                defaultrouter: auto
                defaultrouter6: auto
                depends: none
                devfs_ruleset: '4'
                dhcp: '1'
                enforce_statfs: '2'
                exec_clean: '1'
                exec_created: /usr/bin/true
                exec_fib: '0'
                exec_jail_user: root
                exec_poststart: /usr/bin/true
                exec_poststop: /usr/bin/true
                exec_prestart: /usr/bin/true
                exec_prestop: /usr/bin/true
                exec_start: /bin/sh /etc/rc
                exec_stop: /bin/sh /etc/rc.shutdown
                exec_system_jail_user: '0'
                exec_system_user: root
                exec_timeout: '60'
                host_domainname: none
                host_hostname: ansible-client-pull
                host_hostuuid: ansible_client_pull
                host_time: '1'
                hostid: d6add9ad-8b6e-4316-a226-345a60532b41
                hostid_strict_check: '0'
                interfaces: vnet0:bridge0
                ip4: new
                ip4_addr: none
                ip4_saddrsel: '1'
                ip6: new
                ip6_addr: none
                ip6_saddrsel: '1'
                ip_hostname: '0'
                jail_zfs: '0'
                jail_zfs_dataset: iocage/jails/ansible_client_pull/data
                jail_zfs_mountpoint: none
                last_started: '2026-02-24 22:37:16'
                localhost_ip: none
                login_flags: -f root
                mac_prefix: 5a9cfc
                maxproc: 'off'
                memorylocked: 'off'
                memoryuse: 'off'
                min_dyn_devfs_ruleset: '1000'
                mount_devfs: '1'
                mount_fdescfs: '1'
                mount_linprocfs: '0'
                mount_procfs: '0'
                mountpoint: readonly
                msgqqueued: 'off'
                msgqsize: 'off'
                nat: '0'
                nat_backend: ipfw
                nat_forwards: none
                nat_interface: none
                nat_prefix: '172.16'
                nmsgq: 'off'
                notes: none
                nsem: 'off'
                nsemop: 'off'
                nshm: 'off'
                nthr: 'off'
                openfiles: 'off'
                origin: readonly
                owner: root
                pcpu: 'off'
                plugin_name: none
                plugin_repository: none
                priority: '99'
                pseudoterminals: 'off'
                quota: none
                readbps: 'off'
                readiops: 'off'
                release: 15.0-RELEASE-p3
                reservation: none
                resolver: /etc/resolv.conf
                rlimits: 'off'
                rtsold: '0'
                securelevel: '2'
                shmsize: 'off'
                stacksize: 'off'
                state: up
                stop_timeout: '30'
                swapuse: 'off'
                sync_state: none
                sync_target: none
                sync_tgt_zpool: none
                sysvmsg: new
                sysvsem: new
                sysvshm: new
                template: '0'
                type: jail
                used: readonly
                vmemoryuse: 'off'
                vnet: '1'
                vnet0_mac: 5a9cfc7e220c 5a9cfc7e220d
                vnet0_mtu: auto
                vnet1_mac: none
                vnet1_mtu: auto
                vnet2_mac: none
                vnet2_mtu: auto
                vnet3_mac: none
                vnet3_mtu: auto
                vnet_default_interface: none
                vnet_default_mtu: '1500'
                vnet_interfaces: none
                wallclock: 'off'
                writebps: 'off'
                writeiops: 'off'
            release: 15.0-RELEASE-p3
            state: up
            template: '-'
            type: jail

TASK [Stop: Stop jails.] *******************************************************
ok: [iocage_04]

TASK [Template: Set template.] *************************************************
ok: [iocage_04] => (item=ansible_client_pull)

PLAY RECAP *********************************************************************
iocage_04                  : ok=18   changed=6    unreachable=0    failed=0    skipped=24   rescued=0    ignored=0   

Templates at iocage_04

[iocage_04]#  iocage list -lt
+------+-----------------------+------+-------+----------+-----------------+--------------------+-----+----------+----------+
| JID  |         NAME          | BOOT | STATE |   TYPE   |     RELEASE     |        IP4         | IP6 | TEMPLATE | BASEJAIL |
+======+=======================+======+=======+==========+=================+====================+=====+==========+==========+
| None | ansible_client        | off  | down  | template | 15.0-RELEASE-p3 | DHCP (not running) | -   | -        | no       |
+------+-----------------------+------+-------+----------+-----------------+--------------------+-----+----------+----------+
| None | ansible_client_apache | off  | down  | template | 15.0-RELEASE-p3 | DHCP (not running) | -   | -        | no       |
+------+-----------------------+------+-------+----------+-----------------+--------------------+-----+----------+----------+
| None | ansible_client_pull   | off  | down  | template | 15.0-RELEASE-p3 | DHCP (not running) | -   | -        | no       |
+------+-----------------------+------+-------+----------+-----------------+--------------------+-----+----------+----------+