├── .gitignore ├── roles └── arm-image │ ├── tests │ ├── inventory │ └── test.yml │ ├── vars │ └── main.yml │ ├── defaults │ └── main.yml │ ├── handlers │ └── main.yml │ ├── tasks │ ├── main.yml │ ├── mount.yml │ ├── configure-boot.yml │ ├── resolve.yml │ ├── partscan.yml │ ├── configure-root.yml │ ├── fetch.yml │ └── flash.yml │ ├── templates │ ├── ifcfg-dhcp.j2 │ └── ifcfg.j2 │ ├── README.md │ └── meta │ └── main.yml ├── README.md ├── inventory └── deploy.yml /.gitignore: -------------------------------------------------------------------------------- 1 | all 2 | work 3 | -------------------------------------------------------------------------------- /roles/arm-image/tests/inventory: -------------------------------------------------------------------------------- 1 | localhost 2 | 3 | -------------------------------------------------------------------------------- /roles/arm-image/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # vars file for arm-image -------------------------------------------------------------------------------- /roles/arm-image/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # defaults file for arm-image -------------------------------------------------------------------------------- /roles/arm-image/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # handlers file for arm-image -------------------------------------------------------------------------------- /roles/arm-image/tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | remote_user: root 4 | roles: 5 | - arm-image -------------------------------------------------------------------------------- /roles/arm-image/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for arm-image 3 | - name: "Execute the ===> {{ instruction }} <=== instruction" 4 | include: "{{ instruction }}.yml" 5 | 6 | -------------------------------------------------------------------------------- /roles/arm-image/templates/ifcfg-dhcp.j2: -------------------------------------------------------------------------------- 1 | TYPE=Ethernet 2 | PROXY_METHOD=none 3 | BROWSER_ONLY=no 4 | BOOTPROTO={{ network.bootproto }} 5 | DEFROUTE=yes 6 | IPV4_FAILURE_FATAL=no 7 | IPV6INIT=yes 8 | IPV6_AUTOCONF=yes 9 | IPV6_DEFROUTE=yes 10 | IPV6_FAILURE_FATAL=no 11 | IPV6_ADDR_GEN_MODE=stable-privacy 12 | PREFIX={{ network.prefix }} 13 | NAME={{ network.device }} 14 | DEVICE={{ network.device }} 15 | ONBOOT=yes 16 | AUTOCONNECT_PRIORITY=-999 17 | -------------------------------------------------------------------------------- /roles/arm-image/templates/ifcfg.j2: -------------------------------------------------------------------------------- 1 | TYPE=Ethernet 2 | PROXY_METHOD=none 3 | BROWSER_ONLY=no 4 | BOOTPROTO={{ network.bootproto }} 5 | DEFROUTE=yes 6 | IPV4_FAILURE_FATAL=no 7 | IPV6INIT=yes 8 | IPV6_AUTOCONF=yes 9 | IPV6_DEFROUTE=yes 10 | IPV6_FAILURE_FATAL=no 11 | IPV6_ADDR_GEN_MODE=stable-privacy 12 | IPADDR={{ network.ipaddr }} 13 | PREFIX={{ network.prefix }} 14 | GATEWAY={{ network.gateway }} 15 | DNS1={{ network.dns1 }} 16 | NAME={{ network.device }} 17 | DEVICE={{ network.device }} 18 | ONBOOT=yes 19 | AUTOCONNECT_PRIORITY=-999 20 | -------------------------------------------------------------------------------- /roles/arm-image/tasks/mount.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: make sure local mount stubs exist 3 | become: true 4 | file: 5 | name: "{{ mountstub }}" 6 | mode: '0700' 7 | state: directory 8 | - debug: 9 | msg: "Mounting {{ image }} on {{ mountstub }}" 10 | - debug: 11 | var: partition 12 | - name: "Mount lvm root partition in {{ mountstub }}" 13 | become: true 14 | mount: 15 | src: "{{ image }}" 16 | path: "{{ mountstub }}" 17 | opts: "rw,loop,offset={{ partition.begin | int }}" 18 | fstype: "{{ partition.fstype | regex_replace('fat32', 'vfat')}}" 19 | state: mounted 20 | fstab: /tmp/fstab.dummy 21 | ... 22 | -------------------------------------------------------------------------------- /roles/arm-image/tasks/configure-boot.yml: -------------------------------------------------------------------------------- 1 | - name: Replace kernel parameters line in cmdline.txt 2 | become: true 3 | lineinfile: 4 | path: "{{ location }}/cmdline.txt" 5 | state: present 6 | line: "console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait quiet" 7 | regexp: 'console=.*' 8 | - name: Tell raspberry firmware to make use of the the aarch64 kernel named kernel8 9 | become: true 10 | lineinfile: 11 | path: "{{ location }}/config.txt" 12 | state: present 13 | line: "arm_64bit=1" 14 | regexp: 'arm_64bit=.*' 15 | when: fedora32_url.find("aarch64") != -1 16 | -------------------------------------------------------------------------------- /roles/arm-image/tasks/resolve.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: "Checking for redirect and setting redcheck.stdout_lines.0 to notredirect or the actual url" 3 | shell: 'curl {{ requested_url }} --max-time 5 --max-redirs 0 --silent > /dev/null && curl {{ requested_url }} --max-redirs 0 --silent |grep moved|cut -d\" -f2 || echo notredirect' 4 | args: 5 | warn: no 6 | register: redcheck 7 | - set_fact: 8 | url: 9 | - name: "Use the redirect URL {{ redcheck.stdout_lines.0 }} to avoid redownloading large images" 10 | set_stats: 11 | url: "{{ redcheck.stdout_lines.0 }}" 12 | when: redcheck.stdout_lines.0 != "notredirect" 13 | 14 | - name: "Else use the requested URL {{ requested_url }}" 15 | set_fact: 16 | url: "{{ requested_url }}" 17 | when: redcheck.stdout_lines.0 == "notredirect" 18 | - debug: var=url 19 | ... 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Playbook to deploy Fedora 32 on a Raspberry Pi 4 using the boot partition and kernel from Raspbian. 2 | 3 | First, prepare your systems. Three "roles" ("roles" in the generic sense of the word in English, not "Ansible Roles") need to be performed - 4 | * A system that you run the playbook from 5 | * A system where the images get assembled 6 | * A system where the product hits an SD card. 7 | 8 | These can be the same machine but don't have to be. 9 | 10 | Clone this git repository onto the base system. 11 | 12 | Then, on the base machine: 13 | 14 | # Step 1: 15 | 16 | ```bash 17 | dnf -y install ansible git 18 | ``` 19 | 20 | # Step 2: 21 | edit the inventory file with the settings you want. 22 | 23 | # Step 3: 24 | Run: 25 | 26 | ```bash 27 | cd armbuild 28 | 29 | ansible-playbook -i inventory deploy.yml 30 | ``` 31 | 32 | Add -vvv for gore. 33 | 34 | A writeup of how to use this can be found here: 35 | 36 | https://medium.com/ironhaul/installing-64-bit-fedora-on-the-raspberry-pi-4-d4a665ea65d3 37 | 38 | There's a known issue where if there already is an LVM volume group on your machine named "fedora", the downloaded aarch64 Fedora image won't get properly mounted by the playbook. 39 | 40 | If you need a kernel different from the default Raspbian one, refer: 41 | 42 | https://medium.com/ironhaul/compiling-a-custom-kernel-on-fedora-on-raspberry-pi-9e199731220b 43 | -------------------------------------------------------------------------------- /roles/arm-image/README.md: -------------------------------------------------------------------------------- 1 | Role Name 2 | ========= 3 | 4 | A brief description of the role goes here. 5 | 6 | Requirements 7 | ------------ 8 | 9 | Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required. 10 | 11 | Role Variables 12 | -------------- 13 | 14 | A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well. 15 | 16 | Dependencies 17 | ------------ 18 | 19 | A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles. 20 | 21 | Example Playbook 22 | ---------------- 23 | 24 | Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too: 25 | 26 | - hosts: servers 27 | roles: 28 | - { role: username.rolename, x: 42 } 29 | 30 | License 31 | ------- 32 | 33 | BSD 34 | 35 | Author Information 36 | ------------------ 37 | 38 | An optional section for the role authors to include contact information, or a website (HTML is not allowed). 39 | -------------------------------------------------------------------------------- /roles/arm-image/meta/main.yml: -------------------------------------------------------------------------------- 1 | galaxy_info: 2 | author: your name 3 | description: your role description 4 | company: your company (optional) 5 | 6 | # If the issue tracker for your role is not on github, uncomment the 7 | # next line and provide a value 8 | # issue_tracker_url: http://example.com/issue/tracker 9 | 10 | # Choose a valid license ID from https://spdx.org - some suggested licenses: 11 | # - BSD-3-Clause (default) 12 | # - MIT 13 | # - GPL-2.0-or-later 14 | # - GPL-3.0-only 15 | # - Apache-2.0 16 | # - CC-BY-4.0 17 | license: license (GPL-2.0-or-later, MIT, etc) 18 | 19 | min_ansible_version: 2.9 20 | 21 | # If this a Container Enabled role, provide the minimum Ansible Container version. 22 | # min_ansible_container_version: 23 | 24 | # 25 | # Provide a list of supported platforms, and for each platform a list of versions. 26 | # If you don't wish to enumerate all versions for a particular platform, use 'all'. 27 | # To view available platforms and versions (or releases), visit: 28 | # https://galaxy.ansible.com/api/v1/platforms/ 29 | # 30 | # platforms: 31 | # - name: Fedora 32 | # versions: 33 | # - all 34 | # - 25 35 | # - name: SomePlatform 36 | # versions: 37 | # - all 38 | # - 1.0 39 | # - 7 40 | # - 99.99 41 | 42 | galaxy_tags: [] 43 | # List tags for your role here, one per line. A tag is a keyword that describes 44 | # and categorizes the role. Users find roles by searching for tags. Be sure to 45 | # remove the '[]' above, if you add tags to this list. 46 | # 47 | # NOTE: A tag is limited to a single word comprised of alphanumeric characters. 48 | # Maximum 20 tags per role. 49 | 50 | dependencies: [] 51 | # List your role dependencies here, one per line. Be sure to remove the '[]' above, 52 | # if you add dependencies to this list. 53 | -------------------------------------------------------------------------------- /inventory: -------------------------------------------------------------------------------- 1 | [all] 2 | [all:vars] 3 | workpath=/home/mikishapiro 4 | mntroot=/mnt 5 | targetmachine=flasher1 6 | targetdevice=/dev/sdb 7 | dd_blocksize=524288 8 | # Fedora pre-releases can be taken from nightlies: https://www.happyassassin.net/nightlies.html 9 | # Fedora can be taken from: https://getfedora.org/en/server/download/ 10 | # fedora32_url=https://download.fedoraproject.org/pub/fedora/linux/releases/test/32_Beta/Server/aarch64/images/Fedora-Server-32_Beta-1.2.aarch64.raw.xz 11 | fedora32_url=https://mirror.aarnet.edu.au/pub/fedora/linux/releases/32/Server/aarch64/images/Fedora-Server-32-1.6.aarch64.raw.xz 12 | fedora32checksum_url=https://mirror.aarnet.edu.au/pub/fedora/linux/releases/32/Server/aarch64/images/Fedora-Server-32-1.6-aarch64-CHECKSUM 13 | # From here: https://www.raspberrypi.org/downloads/raspbian/ 14 | raspbian_url=https://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2020-02-14/2020-02-13-raspbian-buster-lite.zip 15 | raspbianchecksum_url=http://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2020-02-14/2020-02-13-raspbian-buster-lite.zip.sha256 16 | pi_bootproto=none 17 | pi_interface=eth0 18 | pi_ip=192.168.1.29 19 | pi_prefix=24 20 | pi_gateway=192.168.1.1 21 | pi_dns=8.8.8.8 22 | #pi_hostname=my-pi-host.my-domain 23 | # pubkey='ssh-rsa AAAA... user@domain.com' 24 | # rootpasswordhash='ROOTHASHHERE' 25 | # To be implemented at a later stage: 26 | # ssid="MYSSID" 27 | # wifi_password="MYWIFIPASSWORD" 28 | # Prevents re-extracting images from downloaded archives: 29 | lazy=true 30 | # Dereferences HTTP redirects (raspbian URLs use them) to find out real filename which helps not redownload it each time 31 | deref=true 32 | # Single host, define to true if running on single host instead of 33 | # multiple hosts to avoid image transfers over network. 34 | single_host=false 35 | [builders] 36 | builder1 37 | 38 | [flashers] 39 | flasher1 40 | -------------------------------------------------------------------------------- /roles/arm-image/tasks/partscan.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for armulator 3 | - set_fact: 4 | partflags: [] 5 | lvm: "" 6 | 7 | - name: Read device information 8 | parted: 9 | device: "{{ image }}" 10 | unit: B 11 | register: raw_part 12 | 13 | - name: Concatenate partition flags to check if there are lvm ones 14 | set_fact: 15 | partflags: "{{ partflags + item.flags }}" 16 | loop: "{{ raw_part.partitions }}" 17 | 18 | - name: Enable lvm handling if an lvm flag is found 19 | set_fact: 20 | lvm: true 21 | loop: "{{ partflags }}" 22 | when: item == "lvm" 23 | 24 | - name: Select the main partition 25 | set_fact: 26 | root_partition: 27 | - "{{ raw_part.partitions|last }}" 28 | when: lvm != true 29 | 30 | - name: Install the image handling packages 31 | become: true 32 | when: lvm == true 33 | package: 34 | state: latest 35 | name: 36 | - kpartx 37 | 38 | - name: Invoke kparts to make lvm partitions in raw image file visible to lvm 39 | become: true 40 | command: "kpartx -av {{ image }}" 41 | when: lvm == true 42 | register: kpartx_output 43 | 44 | - name: Get volume group UUID 45 | become: true 46 | shell: "pvs -o +vg_uuid | grep loop | awk '{print $7}'" 47 | when: lvm == true 48 | register: vguuid_output 49 | 50 | - name: Store volume group UUID 51 | set_fact: 52 | vguuid: "{{ vguuid_output.stdout }}" 53 | when: lvm == true 54 | 55 | - name: Rename volume group 56 | become: true 57 | command: "vgrename {{ vguuid }} fedoraarm" 58 | when: lvm == true 59 | 60 | - name: Set the rootpart if fedora aarch64 lvm is used 61 | set_fact: 62 | root_partition: 63 | - begin: "0" 64 | fstype: "xfs" 65 | when: lvm == true 66 | 67 | - set_fact: 68 | remaining_parts: "{{ raw_part.partitions | difference( root_partition ) }}" 69 | when: lvm != true 70 | 71 | - set_fact: 72 | boot_partition: 73 | - "{{ remaining_parts|last }}" 74 | when: lvm != true 75 | -------------------------------------------------------------------------------- /roles/arm-image/tasks/configure-root.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: create a .ssh directory 3 | become: yes 4 | file: 5 | name: "{{ location }}/root/.ssh" 6 | mode: '0700' 7 | state: directory 8 | when: pubkey is defined 9 | 10 | - name: create a file 11 | become: yes 12 | file: 13 | path: "{{ location }}/root/.ssh/authorized_keys" 14 | mode: '0600' 15 | state: touch 16 | when: pubkey is defined 17 | 18 | - name: Set appropriate permissions on the authorized_keys file 19 | become: yes 20 | lineinfile: 21 | path: "{{ location }}/root/.ssh/authorized_keys" 22 | line: "{{ pubkey }}" 23 | state: present 24 | when: pubkey is defined 25 | 26 | - name: Set root password 27 | become: yes 28 | lineinfile: 29 | path: "{{ location }}/etc/shadow" 30 | state: present 31 | line: 'root:{{ rootpasswordhash }}::0:99999:7:::' 32 | regexp: '^root:' 33 | when: rootpasswordhash is defined 34 | 35 | - name: Set hostname 36 | become: yes 37 | copy: 38 | dest: "{{ location }}/etc/hostname" 39 | content: | 40 | {{ pi_hostname }} 41 | when: pi_hostname is defined 42 | 43 | - name: Permit root login via ssh 44 | become: yes 45 | lineinfile: 46 | path: "{{ location }}/etc/ssh/sshd_config" 47 | state: present 48 | line: "PermitRootLogin yes" 49 | regexp: '^PermitRootLogin' 50 | 51 | - name: Remove boot and boot/efi lines in fstab 52 | become: yes 53 | lineinfile: 54 | path: "{{ location }}/etc/fstab" 55 | state: absent 56 | regexp: '^.* /boot.*' 57 | 58 | - name: Add boot to fstab 59 | become: yes 60 | lineinfile: 61 | path: "{{ location }}/etc/fstab" 62 | state: present 63 | line: "/dev/mmcblk0p1 /boot vfat defaults,noatime 0 0" 64 | regexp: '^.* / .*' 65 | 66 | - name: Replace root line in fstab 67 | become: yes 68 | lineinfile: 69 | path: "{{ location }}/etc/fstab" 70 | state: present 71 | line: "/dev/mmcblk0p2 / ext4 defaults,noatime 0 0" 72 | regexp: '^.* / .*' 73 | 74 | - name: Copy over raspbian kernel modules 75 | become: yes 76 | command: "cp -uvrfp {{ raspbian_location }}/lib/modules {{ location }}/usr/lib" 77 | 78 | - name: Copy over firmware 79 | become: yes 80 | command: "cp -uvrfp {{ raspbian_location }}/lib/firmware {{ location }}/lib" 81 | 82 | - name: Copy over raspbian modprobe.d blacklist 83 | become: yes 84 | command: 'cp -uvrfp {{ raspbian_location }}/etc/modprobe.d/. {{ location }}/etc/modprobe.d/' 85 | 86 | - name: "Deploy the {{ network.device }} network static interface configuration" 87 | become: yes 88 | template: 89 | src: templates/ifcfg.j2 90 | dest: "{{ location }}/etc/sysconfig/network-scripts/ifcfg-{{ network.device }}" 91 | when: network.bootproto != "dhcp" 92 | 93 | - name: "Deploy the {{ network.device }} network dynamic interface configuration" 94 | become: yes 95 | template: 96 | src: templates/ifcfg-dhcp.j2 97 | dest: "{{ location }}/etc/sysconfig/network-scripts/ifcfg-{{ network.device }}" 98 | when: network.bootproto == "dhcp" 99 | 100 | - name: Disable the initial-setup and other unneeded services 101 | become: yes 102 | file: 103 | path: "{{ location }}/etc/systemd/system/multi-user.target.wants/{{ item }}.service" 104 | state: absent 105 | loop: 106 | - initial-setup 107 | - abrtd 108 | - abrt-journal-core 109 | - abrt-oops 110 | - abrt-vmcore 111 | - abrt-xorg 112 | - cups 113 | - nfs-client 114 | - vboxservice 115 | - vmtoolsd 116 | - ModemManager 117 | - rngd 118 | - avahi-daemon 119 | - auditd 120 | - smartd 121 | - libvirtd 122 | - remote-fs 123 | -------------------------------------------------------------------------------- /roles/arm-image/tasks/fetch.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: make sure workpath exists 3 | file: 4 | name: "{{ workpath }}" 5 | state: directory 6 | 7 | - name: set a variable to contain the escaped octal value of doublequotes 8 | set_fact: 9 | dq: \42 10 | 11 | - name: "Checking for redirect and setting redcheck.stdout_lines.0 to notredirect or the actual url" 12 | shell: "curl {{ requested_url }} --max-time 5 --max-redirs 0 --silent |grep moved > /dev/null && curl {{ requested_url }} --max-redirs 0 --silent |grep moved|cut -d$'{{ dq }}' -f2 || echo notredirect" 13 | args: 14 | warn: no 15 | register: redcheck 16 | when: deref != true 17 | 18 | - name: "Use the redirect URL to avoid redownloading large images" 19 | set_fact: 20 | url: "{{ redcheck.stdout_lines.0 }}" 21 | when: 22 | - redcheck.stdout_lines.0 != "notredirect" 23 | - deref != true 24 | 25 | - name: "Else use the requested URL {{ requested_url }}" 26 | set_fact: 27 | url: "{{ requested_url }}" 28 | when: 29 | - redcheck.stdout_lines.0 == "notredirect" 30 | - deref != true 31 | 32 | - name: "Use the requested URL {{ requested_url }} if deref is off" 33 | set_fact: 34 | url: "{{ requested_url }}" 35 | when: 36 | deref == true 37 | 38 | - name: "Checking for redirect and setting redcheck.stdout_lines.0 to notredirect or the actual url" 39 | shell: "curl {{ requested_checksum_url }} --max-time 5 --max-redirs 0 --silent |grep moved > /dev/null && curl {{ requested_url }} --max-redirs 0 --silent |grep moved|cut -d$'{{ dq }}' -f2 || echo notredirect" 40 | args: 41 | warn: no 42 | register: redcheck 43 | when: deref != true 44 | 45 | - name: "Use the redirect URL to avoid redownloading large images" 46 | set_fact: 47 | url: "{{ redcheck.stdout_lines.0 }}" 48 | when: 49 | - redcheck.stdout_lines.0 != "notredirect" 50 | - deref != true 51 | 52 | - name: "Else use the requested URL {{ requested_checksum_url }}" 53 | set_fact: 54 | urlchecksum: "{{ requested_checksum_url }}" 55 | when: 56 | - redcheck.stdout_lines.0 == "notredirect" 57 | - deref != true 58 | 59 | - name: "Use the requested URL {{ requested_checksum_url }} if deref is off" 60 | set_fact: 61 | urlchecksum: "{{ requested_checksum_url }}" 62 | when: 63 | deref == true 64 | 65 | - name: "Download the image checksum from {{ urlchecksum }}" 66 | get_url: 67 | url: "{{ urlchecksum }}" 68 | dest: "{{ workpath }}/{{ urlchecksum | basename }}" 69 | register: download 70 | - debug: var=download 71 | 72 | - name: "Get the content of {{ workpath }}/{{ urlchecksum | basename }}" 73 | slurp: 74 | src: "{{ workpath }}/{{ urlchecksum | basename }}" 75 | register: image_checksum_b64 76 | 77 | - name: Get the actual checksum 78 | set_fact: 79 | image_checksum: "{{ image_checksum_b64.content | b64decode | regex_search('[0-9a-f]{64}') }}" 80 | - debug: var=image_checksum 81 | 82 | - name: "Download the image from {{ url }}" 83 | get_url: 84 | url: "{{ url }}" 85 | dest: "{{ workpath }}/{{ url | basename }}" 86 | checksum: 'sha256:{{ image_checksum }}' 87 | register: download 88 | - debug: var=download 89 | 90 | - name: Set the name of the file that just got extracted 91 | set_fact: 92 | extractedfile: "{{ download.dest | regex_replace('.xz') | regex_replace('.zip', '.img') }}" 93 | - debug: var=extractedfile 94 | - name: "Ensure that {{ extractedfile }} exists" 95 | stat: 96 | path: "{{ extractedfile }}" 97 | register: raw_image_stat_result 98 | 99 | - debug: var=raw_image_stat_result 100 | - debug: var=url 101 | 102 | - name: Extract xz archives when they are not yet extracted 103 | command: "unxz -fk {{ download.dest }}" 104 | when: 105 | - download.dest is search("xz") 106 | - not raw_image_stat_result.stat.exists 107 | 108 | - name: Extract xz archives when something named like their content is there 109 | command: "unxz -fk {{ download.dest }}" 110 | when: 111 | - not download.dest.find("zip") 112 | - raw_image_stat_result.stat.exists 113 | - not lazy|bool 114 | 115 | - name: Extract zip archives 116 | unarchive: 117 | src: "{{ download.dest }}" 118 | remote_src: true 119 | dest: "{{ workpath }}" 120 | keep_newer: true 121 | when: download.dest is search("zip") 122 | ... 123 | -------------------------------------------------------------------------------- /roles/arm-image/tasks/flash.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # tasks file for image-flash 3 | 4 | # using {{ dd_blocksize }} blocks (tested with 512K blocks represented numerically as 524288) 5 | # {{ mbr }}, {{ boot }} and {{ root }} have the following structure 6 | # mbr|boot|root: 7 | # image: path to image file 8 | # partition: 9 | # begin: first byte (represented with one digit past decimal point. must be run through int for seeks and additive math.) 10 | # end: last byte (represented with one digit past decimal point. must be run through int for seeks and additive math.) 11 | # size: size in bytes (represented with one digit past decimal point. must be run through int for seeks and additive math.) 12 | # note 13 | # 14 | # due to addressging starting at zero, begin and size will be even and dividable by blocksize to convert to a blocks figure. 15 | # end will be odd and requires 1 be added to it before dividing by blocksize to be converted to a blocks figure. 16 | - name: Display parameters provided 17 | debug: 18 | var: "{{ item }}" 19 | loop: 20 | - mbr 21 | - boot 22 | - root 23 | - dd_blocksize 24 | 25 | 26 | - name: Wipe out the first 300MB of the target disk to avoid parted complaints 27 | delegate_to: "{{ targetmachine }}" 28 | become: true 29 | shell: "dd if=/dev/zero of={{ targetdevice }} bs=1M count=300" 30 | 31 | - name: "stream the mbr and boot images to {{ targetmachine }} and dd them to {{ targetdevice }}" 32 | shell: '( dd if={{ mbr.image }} count={{ (mbr.partition.size|int/dd_blocksize)|int }} bs={{ dd_blocksize }}; dd if={{ boot.image }} bs={{ dd_blocksize }} skip={{ (boot.partition.begin|int/dd_blocksize)|int }} count={{ (boot.partition.size|int/dd_blocksize)|int }} ) | ssh root@{{ targetmachine }} dd of={{ targetdevice }} bs={{ dd_blocksize }}' 33 | when: not single_host|bool 34 | 35 | - name: "write the mbr and boot images through dd to {{ targetdevice }}" 36 | become: true 37 | shell: '( dd if={{ mbr.image }} count={{ (mbr.partition.size|int/dd_blocksize)|int }} bs={{ dd_blocksize }}; dd if={{ boot.image }} bs={{ dd_blocksize }} skip={{ (boot.partition.begin|int/dd_blocksize)|int }} count={{ (boot.partition.size|int/dd_blocksize)|int }} ) | dd of={{ targetdevice }} bs={{ dd_blocksize }}' 38 | when: single_host|bool 39 | 40 | - name: "Remove partition 2 on {{ targetdevice }} on {{ targetmachine }}" 41 | delegate_to: "{{ targetmachine }}" 42 | become: true 43 | parted: 44 | device: "{{ targetdevice }}" 45 | number: 2 46 | state: absent 47 | 48 | - name: Create a new root partition on {{ targetdevice }} on {{ targetmachine }}" 49 | delegate_to: "{{ targetmachine }}" 50 | become: true 51 | parted: 52 | device: "{{ targetdevice }}" 53 | number: 2 54 | state: present 55 | part_start: "{{ ((mbr.partition.size|int + boot.partition.size|int)|int/1024)|int }}KiB" 56 | 57 | - name: "Create a filesystem on partition {{ targetdevice }}2 on {{ targetmachine }}" 58 | delegate_to: "{{ targetmachine }}" 59 | become: true 60 | filesystem: 61 | dev: "{{ targetdevice }}2" 62 | fstype: ext4 63 | 64 | - name: "Ensure mount stubs on {{ targetmachine }} exist" 65 | delegate_to: "{{ targetmachine }}" 66 | become: true 67 | file: 68 | name: "{{ mntroot }}/piroot" 69 | mode: '0700' 70 | state: directory 71 | 72 | - name: "mount {{ targetdevice }}2 on {{ targetmachine }}" 73 | delegate_to: "{{ targetmachine }}" 74 | become: true 75 | mount: 76 | src: "{{ targetdevice }}2" 77 | path: "{{ mntroot }}/piroot" 78 | state: mounted 79 | fstab: /tmp/fstab.dummy 80 | fstype: ext4 81 | 82 | - name: "copy the Fedora32 root filesystem as files to {{ targetdevice }}2 on {{ targetmachine }}" 83 | become: true 84 | shell: "tar cfp - . | ssh root@{{ targetmachine }} 'cd {{ mntroot }}/piroot; tar xvfp -'" 85 | args: 86 | chdir: "{{ mntroot }}/fedora32/root" 87 | warn: no 88 | when: not single_host|bool 89 | 90 | - name: "copy the Fedora32 root filesystem as files to {{ targetmachine }}" 91 | become: true 92 | shell: "tar cpf - . | tar -C {{ mntroot }}/piroot -xvpf -" 93 | args: 94 | chdir: "{{ mntroot }}/fedora32/root" 95 | warn: no 96 | when: single_host|bool 97 | 98 | - name: "chmod the / directory inside {{ targetdevice }}2 on {{ targetmachine }}" 99 | delegate_to: "{{ targetmachine }}" 100 | become: true 101 | file: 102 | path: "{{ mntroot }}/piroot" 103 | state: directory 104 | mode: '0755' 105 | 106 | - name: Unmount the target filesystem 107 | delegate_to: "{{ targetmachine }}" 108 | become: true 109 | mount: 110 | path: "{{ mntroot }}/piroot" 111 | state: unmounted 112 | -------------------------------------------------------------------------------- /deploy.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Play to deploy an operating system on a Raspberry 3 | hosts: builders 4 | # become: true 5 | # connection: local 6 | tasks: 7 | - debug: var=workpath 8 | # Set up Fedora 32 9 | - name: Download and prep Fedora 32 10 | vars: 11 | instruction: fetch 12 | requested_url: "{{ fedora32_url }}" 13 | requested_checksum_url: "{{ fedora32checksum_url }}" 14 | include_role: 15 | name: arm-image 16 | 17 | - name: "Find boot and root partitions in extracted image {{ extractedfile }}" 18 | vars: 19 | instruction: partscan 20 | image: "{{ extractedfile }}" 21 | include_role: 22 | name: arm-image 23 | 24 | - name: "Mount non-lvm fedora partition" 25 | vars: 26 | instruction: mount 27 | image: "{{ extractedfile }}" 28 | mountstub: "{{ mntroot }}/fedora32/root" 29 | partition: "{{ root_partition.0 }}" 30 | include_role: 31 | name: arm-image 32 | when: lvm != true 33 | 34 | - name: "Mount lvm fedora partition" 35 | vars: 36 | image: "/dev/fedoraarm/root" 37 | instruction: mount 38 | mountstub: "{{ mntroot }}/fedora32/root" 39 | partition: "{{ root_partition.0 }}" 40 | include_role: 41 | name: arm-image 42 | when: lvm == true 43 | 44 | - name: Download and prep Raspbian 45 | vars: 46 | instruction: fetch 47 | requested_url: "{{ raspbian_url }}" 48 | requested_checksum_url: "{{ raspbianchecksum_url }}" 49 | include_role: 50 | name: arm-image 51 | 52 | - name: "Find boot and root partitions in extracted image {{ extractedfile }}" 53 | vars: 54 | instruction: partscan 55 | image: "{{ extractedfile }}" 56 | include_role: 57 | name: arm-image 58 | 59 | - name: Keep partition table data about the root partition to dump to flash later 60 | set_fact: 61 | flash_root: 62 | image: "{{ extractedfile }}" 63 | partition: "{{ root_partition.0 }}" 64 | 65 | - name: "Mount non-lvm root partition in {{ mntroot }}/raspbian/root" 66 | vars: 67 | instruction: mount 68 | mountstub: "{{ mntroot }}/raspbian/root" 69 | image: "{{ extractedfile }}" 70 | partition: "{{ root_partition.0 }}" 71 | include_role: 72 | name: arm-image 73 | 74 | - name: Configure the fedora root filesystem 75 | vars: 76 | instruction: configure-root 77 | location: "{{ mntroot }}/fedora32/root" 78 | raspbian_location: "{{ mntroot }}/raspbian/root" 79 | network: 80 | bootproto: "{{ pi_bootproto }}" 81 | device: "{{ pi_interface }}" 82 | ipaddr: "{{ pi_ip }}" 83 | prefix: "{{ pi_prefix }}" 84 | gateway: "{{ pi_gateway }}" 85 | dns1: "{{ pi_dns }}" 86 | include_role: 87 | name: arm-image 88 | 89 | - name: Unmount the root filesystems 90 | become: true 91 | mount: 92 | path: "{{ item }}" 93 | state: unmounted 94 | loop: 95 | - "{{ mntroot }}/raspbian/root" 96 | 97 | - name: "Mount the boot partition in {{ mntroot }}/raspbian/boot" 98 | vars: 99 | instruction: mount 100 | mountstub: "{{ mntroot }}/raspbian/boot" 101 | image: "{{ extractedfile }}" 102 | partition: "{{ boot_partition.0 }}" 103 | include_role: 104 | name: arm-image 105 | 106 | - name: Keep partition table data about the boot partition to dump to flash later 107 | set_fact: 108 | flash_boot: 109 | image: "{{ extractedfile }}" 110 | partition: "{{ boot_partition.0 }}" 111 | 112 | - name: Keep partition table data about the master boot record to dump to flash later 113 | set_fact: 114 | flash_mbr: 115 | image: "{{ extractedfile }}" 116 | partition: 117 | begin: 0 118 | end: "{{ (flash_boot.partition.begin|int - 1) }}" 119 | size: "{{ flash_boot.partition.begin }}" 120 | 121 | - name: Perform any required changes to the boot filesystem 122 | vars: 123 | instruction: configure-boot 124 | location: "{{ mntroot }}/raspbian/boot" 125 | init: "/sbin/init" 126 | include_role: 127 | name: arm-image 128 | 129 | - name: Unmount the boot filesystems 130 | become: true 131 | mount: 132 | path: "{{ item }}" 133 | state: unmounted 134 | loop: 135 | - "{{ mntroot }}/raspbian/boot" 136 | 137 | - debug: var=flash_mbr 138 | - debug: var=flash_boot 139 | - debug: var=flash_root 140 | 141 | - name: Write to flash 142 | vars: 143 | instruction: flash 144 | mbr: '{{ flash_mbr }}' 145 | boot: '{{ flash_boot }}' 146 | root: '{{ flash_root }}' 147 | include_role: 148 | name: arm-image 149 | 150 | - name: Unmount the root filesystems 151 | become: true 152 | mount: 153 | path: "{{ item }}" 154 | state: unmounted 155 | loop: 156 | - "{{ mntroot }}/fedora32/root" 157 | 158 | - name: "Clean up the source Fedora LVM loopback devices" 159 | become: true 160 | shell: "lvchange -a n fedoraarm ; kpartx -d /dev/loop0 ; losetup -d /dev/loop0" 161 | ... 162 | --------------------------------------------------------------------------------