├── .dockerignore ├── .github └── workflows │ ├── dockerhub.yml │ ├── shellcheck.yml │ └── tests.yml ├── .gitignore ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── README.md ├── action.yml ├── docker-compose.yml ├── examples ├── Jetson.Pifile ├── Module-Hello.Pifile ├── RPi-OpenWRT.Pifile └── RPi-RaspberryPiOSLite.Pifile ├── modules ├── chroot.sh ├── env.sh ├── error.sh ├── esceval.sh ├── from_remote.sh ├── mount.sh ├── path.sh ├── pifile.sh ├── qemu.sh ├── resolv_conf.sh └── workdir.sh ├── pimod.sh ├── renovate.json └── stages ├── 00-commands.sh ├── 10-setup.sh ├── 20-prepare.sh ├── 30-chroot.sh └── 40-postprocess.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | .gitignore -------------------------------------------------------------------------------- /.github/workflows/dockerhub.yml: -------------------------------------------------------------------------------- 1 | name: Build and upload DockerHub image 2 | 3 | on: [push] 4 | 5 | env: 6 | DOCKERHUB_USER: hoechst 7 | DOCKER_IMAGE: nature40/pimod 8 | 9 | permissions: 10 | id-token: write 11 | contents: write 12 | 13 | jobs: 14 | build-all: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v5 19 | with: 20 | path: . 21 | 22 | - name: Log in to Docker Hub 23 | uses: docker/login-action@v3 24 | with: 25 | # login uses access token: https://docs.docker.com/docker-hub/access-tokens/ 26 | username: ${{ env.DOCKERHUB_USER}} 27 | password: ${{ secrets.DOCKERHUB_TOKEN }} 28 | 29 | - name: Extract metadata (tags, labels) for Docker 30 | id: meta 31 | uses: docker/metadata-action@v5 32 | with: 33 | images: ${{ env.DOCKER_IMAGE }} 34 | flavor: | 35 | latest=true 36 | 37 | - name: Set up QEMU 38 | uses: docker/setup-qemu-action@v3 39 | 40 | - name: Set up Docker Buildx 41 | uses: docker/setup-buildx-action@v3 42 | 43 | - name: Build and push Docker image 44 | uses: docker/build-push-action@v6 45 | with: 46 | context: . 47 | push: true 48 | platforms: linux/amd64,linux/arm64,linux/arm/v7 49 | tags: ${{ steps.meta.outputs.tags }} 50 | labels: ${{ steps.meta.outputs.labels }} 51 | -------------------------------------------------------------------------------- /.github/workflows/shellcheck.yml: -------------------------------------------------------------------------------- 1 | name: shellcheck 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | shellcheck: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Checkout repository 11 | uses: actions/checkout@v5 12 | with: 13 | submodules: recursive 14 | 15 | - name: Shellcheck pimod.sh 16 | run: shellcheck -s bash -x pimod.sh 17 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test-local: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | image: [ "RPi-OpenWRT", "RPi-RaspberryPiOSLite" ] 12 | 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v5 16 | with: 17 | submodules: recursive 18 | 19 | - name: Install dependencies 20 | run: sudo apt-get update && sudo apt-get install -y binfmt-support exfatprogs fdisk file git kpartx lsof p7zip-full qemu-user-static unzip wget xz-utils units 21 | shell: bash 22 | 23 | - name: Run pimod OpenWRT example 24 | run: sudo ./pimod.sh examples/${{ matrix.image }}.Pifile 25 | 26 | test-action: 27 | runs-on: ubuntu-latest 28 | 29 | steps: 30 | - name: Checkout repository 31 | uses: actions/checkout@v5 32 | with: 33 | submodules: recursive 34 | 35 | - name: Run pimod OpenWRT example 36 | uses: Nature40/pimod@HEAD 37 | with: 38 | pifile: examples/RPi-OpenWRT.Pifile 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.img 2 | .cache/ 3 | CHANGELOG.md 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## How to draft a release? 8 | 1. Update this `CHANGELOG.md` file: 9 | 1. Add a new headline for the next release version below _Unreleased_. 10 | 2. Add a link for the next release similar to the others to the bottom and update the _Unreleased_ URL. 11 | 2. Update the `version` within the `Dockerfile`. 12 | 3. Create a commit for the release. 13 | 4. Create a (signed) git tag in the form of `vX.Y.Z`, e.g., `v0.1.0` or `v1.2.3`. 14 | 5. Push both the commit and the tag. 15 | 6. Create a GitHub release via the web UI. 16 | The title should be `${VERSION} - ${YYYY-MM-DD}` and the description should contain the section from this `CHANGELOG.md`. 17 | 18 | ## [Unreleased] 19 | 20 | ## [0.8.0] - 2025-10-22 21 | ### Added 22 | - New `ZERO` command to fill unused filesystem space with zeros for better image compression. 23 | 24 | ### Changed 25 | - Updated Docker base image from debian:bookworm-slim to debian:trixie-slim. 26 | 27 | ## [0.7.0] - 2025-01-07 28 | ### Added 29 | - New `ADDPART` command to add partitions of a given size, partition type and file system to the image. 30 | 31 | ### Changed 32 | - Updated CI badges. 33 | - Updated actions/checkout action to v5. 34 | 35 | ## [0.6.1] - 2024-07-08 36 | ### Added 37 | - Building Docker images for linux/arm/v7. 38 | - Configure Renovate for automated updates. 39 | - Installed git in docker container to support git-based versioning 40 | 41 | ### Changed 42 | - Reworked the README to ease readability and understanding of pimod's internals. 43 | 44 | ## [0.6.0] - 2022-02-12 45 | ### Added 46 | - New -r/--resolv flag to select which /etc/resolv.conf to use. 47 | 48 | ### Fixed 49 | - Apply `ENV` variables also to `HOST` commands. 50 | 51 | ## [0.5.0] - 2022-01-17 52 | ### Added 53 | - New `ENV` command for environment variables. 54 | - New `EXTRACT` command to copy files back to the host; @sensslen. 55 | 56 | ## [0.4.4] - 2021-11-09 57 | ### Added 58 | - Bind /sys, allow the usage of docker inside pimod 59 | 60 | ## [0.4.3] - 2021-11-09 61 | ### Fixed 62 | - PARTUUIDs got rewritten by parted - replaced by sfdisk 63 | 64 | ## [0.4.2] - 2021-11-04 65 | ### Fixed 66 | - Limitedness of PUMP command 67 | 68 | ## [0.4.1] - 2021-11-02 69 | ### Fixed 70 | - Shell-escaped RUN command strings by esceval 71 | 72 | ## [0.4.0] - 2021-10-07 73 | ### Added 74 | - WORKDIR command to specify the working directory within the image. 75 | 76 | ### Fixed 77 | - Brought latest version of pimod back to GitHub Marketplace. 78 | 79 | ## [0.3.1] - 2021-10-04 80 | ### Added 81 | - pimod.sh -t: trace executed commands for debugging. 82 | - Started a CHANGELOG.md with prior changes. 83 | 84 | ### Changed 85 | - FROM: document PARTITION_NO argument. 86 | 87 | ### Fixed 88 | - PUMP: fix behavior for bigger partition tables. 89 | - Print Warning on QEMU binary fmt loading err. 90 | 91 | ## [0.3.0] - 2021-09-23 92 | ### Added 93 | - Adjust GitHub Actions for Pull Requests. 94 | - Add INCLUDE command; @aniongithub. 95 | 96 | ## [0.2.2] - 2021-05-02 97 | ### Changed 98 | - Printing file system usage information, if PUMP was used. 99 | - Warn for Windows/DOS newlines. 100 | 101 | ### Fixed 102 | - qemu_setup: determine path by command -v 103 | - from_remote_fetch: delete image if download failed. 104 | 105 | ## [0.2.1] - 2021-01-17 106 | ### Fixed 107 | - Changed GitHub Action name for marketplace submission. 108 | - Successful e2fsck lead to failing build. 109 | 110 | ## [0.2] - 2020-12-01 111 | ### Added 112 | - Introducing shellcheck CI integration. 113 | - Added paper to readme, . 114 | - Implemented reusable GitHub Action. 115 | 116 | ### Changed 117 | - Basing Docker container on debian:bullseye-slim. 118 | 119 | ### Fixed 120 | - Disable bash error abort in file name parsing. 121 | - Fixed a bug, where killing of remaining processes failed. 122 | - Adjusted Dockerfile to reflect project structure. 123 | 124 | ## [0.1] - 2020-04-26 125 | ### Added 126 | - Initial release of a working version of pimod. 127 | 128 | [Unreleased]: https://github.com/Nature40/pimod/compare/v0.8.0...HEAD 129 | [0.8.0]: https://github.com/Nature40/pimod/compare/v0.7.0...v0.8.0 130 | [0.7.0]: https://github.com/Nature40/pimod/compare/v0.6.1...v0.7.0 131 | [0.6.1]: https://github.com/Nature40/pimod/compare/v0.6.0...v0.6.1 132 | [0.6.0]: https://github.com/Nature40/pimod/compare/v0.5.0...v0.6.0 133 | [0.5.0]: https://github.com/Nature40/pimod/compare/v0.4.4...v0.5.0 134 | [0.4.4]: https://github.com/Nature40/pimod/compare/v0.4.3...v0.4.4 135 | [0.4.3]: https://github.com/Nature40/pimod/compare/v0.4.2...v0.4.3 136 | [0.4.2]: https://github.com/Nature40/pimod/compare/v0.4.1...v0.4.2 137 | [0.4.1]: https://github.com/Nature40/pimod/compare/v0.4.0...v0.4.1 138 | [0.4.0]: https://github.com/Nature40/pimod/compare/v0.3.1...v0.4.0 139 | [0.3.1]: https://github.com/Nature40/pimod/compare/v0.3.0...v0.3.1 140 | [0.3.0]: https://github.com/Nature40/pimod/compare/v0.2.2...v0.3.0 141 | [0.2.2]: https://github.com/Nature40/pimod/compare/v0.2.1...v0.2.2 142 | [0.2.1]: https://github.com/Nature40/pimod/compare/v0.2...v0.2.1 143 | [0.2]: https://github.com/Nature40/pimod/compare/v0.1...v0.2 144 | [0.1]: https://github.com/Nature40/pimod/releases/tag/v0.1 145 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:trixie-slim 2 | 3 | LABEL description="Reconfigure Raspberry Pi images with an easy, Docker-like configuration file" 4 | LABEL maintainer="hoechst@mathematik.uni-marburg.de" 5 | LABEL version="0.8.0" 6 | 7 | RUN bash 8 | 9 | RUN apt-get update && \ 10 | apt-get install -y \ 11 | binfmt-support \ 12 | exfatprogs \ 13 | e2fsprogs \ 14 | fdisk \ 15 | file \ 16 | git \ 17 | kpartx \ 18 | lsof \ 19 | p7zip-full \ 20 | qemu-user-static \ 21 | unzip \ 22 | wget \ 23 | xz-utils \ 24 | units 25 | 26 | RUN mkdir /pimod 27 | COPY . /pimod/ 28 | 29 | ENV PATH="/pimod:${PATH}" 30 | ENV PIMOD_CACHE=".cache" 31 | 32 | WORKDIR /pimod 33 | CMD ["pimod.sh", "Pifile"] 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pimod 2 | [![CI: Tests](https://github.com/Nature40/pimod/actions/workflows/tests.yml/badge.svg)](https://github.com/Nature40/pimod/actions/workflows/tests.yml) 3 | [![CI: Shellcheck](https://github.com/Nature40/pimod/actions/workflows/shellcheck.yml/badge.svg)](https://github.com/Nature40/pimod/actions/workflows/shellcheck.yml) 4 | [![CI: Build and upload DockerHub image](https://github.com/Nature40/pimod/actions/workflows/dockerhub.yml/badge.svg)](https://github.com/Nature40/pimod/actions/workflows/dockerhub.yml) 5 | [![Docker Hub: Version](https://img.shields.io/docker/v/nature40/pimod?color=blue&label=Docker%20Hub&logo=docker&logoColor=lightgrey&sort=semver)](https://hub.docker.com/r/nature40/pimod/tags) 6 | 7 | Reconfigure Raspberry Pi images with an easy, Docker-like configuration file. 8 | 9 | ## About 10 | pimod overtakes a given Raspberry Pi image file by mounting a copy and modifying it within a QEMU chroot. 11 | This allows the execution of a Pi's ARM code on whatever target, e.g., a x86\_64 host. 12 | 13 | To ease the usability, a Docker-inspired recipe, called the Pifile, is used to instrument pimod. 14 | 15 | ``` 16 | # Example Pifile to create a customized version of the Raspberry Pi OS Lite 17 | 18 | # Based on a remote image, which will be cached locally, create the altered raspi_example.img file 19 | FROM https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-02-22/2023-02-21-raspios-bullseye-arm64-lite.img.xz 20 | TO rapsi_example.img 21 | 22 | # Increase the image by 100 MB 23 | PUMP 100M 24 | 25 | # Install an ssh key from local sources 26 | RUN mkdir -p /home/pi/.ssh 27 | INSTALL id_rsa.pub /home/pi/.ssh/authorized_keys 28 | 29 | # Enable the serial console and SSH 30 | RUN raspi-config nonint do_serial 0 31 | RUN raspi-config nonint do_ssh 0 32 | 33 | # Install the important cowsay util 34 | RUN apt-get update 35 | RUN apt-get install -y cowsay 36 | ``` 37 | 38 | ## Installation, Usage 39 | ``` 40 | Usage: pimod.sh [Options] Pifile 41 | 42 | Options: 43 | -c --cache DEST Define cache location. 44 | -d --debug Debug on failure; run an interactive shell before tear down. 45 | -h --help Print this help message. 46 | -r --resolv TYPE Specify which /etc/resolv.conf file to use for networking. 47 | By default, TYPE "auto" is used, which prefers an already 48 | existing resolv.conf, only to be replaced by the host's if 49 | missing. 50 | TYPE "guest" never mounts the host's file within the guest, 51 | even when such a file is absent within the image. 52 | TYPE "host" always uses the host's file within the guest. 53 | Be aware that when run within Docker, the host's file might 54 | be Docker's resolv.conf file. 55 | -t --trace Trace each executed command for debugging. 56 | ``` 57 | 58 | ### Docker 59 | #### Getting or Building the Docker Image 60 | There are pre-built images available on [Docker Hub](https://hub.docker.com/r/nature40/pimod): 61 | 62 | ```sh 63 | docker pull nature40/pimod 64 | ``` 65 | 66 | Alternatively, you can simply build the image yourself locally. 67 | This is essential for development, among other things: 68 | 69 | ```sh 70 | git clone https://github.com/Nature40/pimod.git 71 | cd pimod 72 | docker build -t nature40/pimod . 73 | ``` 74 | 75 | #### Using the Docker Image 76 | Afterwards, the Docker image can either be used by `docker` or `docker compose`: 77 | 78 | ```sh 79 | # Using Docker: 80 | docker run --rm --privileged -v $PWD:/pimod nature40/pimod pimod.sh examples/RPi-OpenWRT.Pifile 81 | 82 | # Using Docker Compose: 83 | docker compose run nature40/pimod pimod.sh examples/RPi-OpenWRT.Pifile 84 | ``` 85 | 86 | ### Debian 87 | Of course, Docker isn't really necessary and pimod can also be used on, e.g., a Debian directly: 88 | 89 | ```sh 90 | sudo apt-get install \ 91 | binfmt-support \ 92 | fdisk \ 93 | file \ 94 | kpartx \ 95 | lsof \ 96 | p7zip-full \ 97 | qemu-user-static \ 98 | unzip \ 99 | wget \ 100 | xz-utils \ 101 | units 102 | 103 | sudo ./pimod.sh Pifile 104 | ``` 105 | 106 | ### GitHub Actions 107 | Pimod can also be used as a GitHub Action and is available on the [marketplace](https://github.com/marketplace/actions/run-pimod). 108 | 109 | ```yml 110 | name: tests 111 | on: push 112 | 113 | jobs: 114 | build: 115 | runs-on: ubuntu-latest 116 | steps: 117 | - name: Checkout repository 118 | uses: actions/checkout@v3 119 | with: 120 | submodules: recursive 121 | - name: Run pimod OpenWRT example 122 | uses: Natur40/pimod@master 123 | with: 124 | pifile: examples/RPi-OpenWRT.Pifile 125 | ``` 126 | 127 | ## Pifile 128 | The Pifile contains commands to modify the image. 129 | 130 | Those commands are grouped in stages which pimod executes in their corresponding order. 131 | 132 | - First, all _setup stage_ commands are being executed to download the base image and configure the output. 133 | - The _prepare stage_ follows which pre-flight commands, e.g., resizing the output image. 134 | - The action happens in the _chroot stage_ where the QEMU chroot is built, commands are executed within, files are copied and so on. 135 | - Finally, the _postprocess stage_ might clean up some things. 136 | 137 | However, as the Pifile being just a Bash script by itself and the commands are functions, which are loaded in different stages, Bash scripting is possible within the Pifile to some extend. 138 | 139 | More internals are documented in our [our scientific paper](https://jonashoechst.de/assets/papers/hoechst2020pimod.pdf). 140 | If you stumble upon details there that you think belong in this README, feel free to create an issue or pull request. 141 | 142 | ### Example 143 | ``` 144 | $ cat Upgrade.Pifile 145 | FROM 2018-11-13-raspbian-stretch-lite.img 146 | 147 | PUMP 100M 148 | 149 | RUN raspi-config nonint do_serial 0 150 | 151 | RUN apt-get update 152 | RUN bash -c 'DEBIAN_FRONTEND=noninteractive apt-get -y dist-upgrade' 153 | RUN apt-get install -y sl 154 | 155 | # The Upgrade.Pifile will create, called by the following command, a new 156 | # Upgrade.img image based on the given Raspbian image. This image's size is 157 | # increased about 100MB, has an enabled UART/serial output, the latest software 158 | # and sl installed. 159 | $ sudo ./pimod.sh Upgrade.Pifile 160 | 161 | # Write the new image to a SD card present at /dev/sdc. 162 | $ dd if=Upgrade.img of=/dev/sdc bs=4M status=progress 163 | ``` 164 | 165 | Further and more expressive examples are available in this repository's `./example` directory. 166 | Please take a look and feel free to submit your own examples if they are covering a current blind spot. 167 | 168 | ### Commands 169 | #### Stage independent 170 | ##### `INCLUDE PATH_TO_PIFILE` 171 | `INCLUDE` includes the provided Pifile in the current one for modularity and re-use. 172 | The included file _has_ to have a `.Pifile` extension which need not be specified. 173 | 174 | #### 1. Setup Stage 175 | ##### `FROM PATH_TO_IMAGE [PARTITION_NO]`, `FROM URL [PARTITION_NO]` 176 | `FROM` sets the `SOURCE_IMG` variable to a target. 177 | This might be a local file or a remote URL, which will be downloaded. 178 | This file will become the base for the new image. 179 | 180 | By default, the Raspberry Pi's default partition number 2 will be used, but can be altered for other targets. 181 | 182 | ##### `TO PATH_TO_IMAGE` 183 | `TO` sets the `DEST_IMG` variable to the given file. 184 | This file will contain the new image. 185 | Existing files will be overridden. 186 | 187 | Instead of calling `TO`, the Pifile's filename can also indicate the output file, if the Pifile ends with *".Pifile"*. 188 | The part before this suffix will be the new `DEST_IMG`. 189 | 190 | If neither `TO` is called nor the Pifile indicates the output, `DEST_IMG` will default to *rpi.img* in the source file's directory. 191 | 192 | ##### `INPLACE FROM_ARGS...` 193 | `INPLACE` does not create a copy of the image, but performs all further operations on the given image. 194 | This is an alternative to `FROM` and `TO`. 195 | 196 | #### 2. Prepare Stage 197 | ##### `PUMP SIZE` 198 | `PUMP` increases the image's size about the given amount (suffixes K, M, G are allowed). 199 | 200 | ##### `ADDPART SIZE PTYPE FS` 201 | `PUMP` appends a partition of the size (suffixes K, M, G are allowed) using a partion type and file system (ext4, exfat, ...). 202 | 203 | #### 3. Chroot Stage 204 | ##### `INSTALL SOURCE DEST` 205 | `INSTALL` installs a given file or directory into the destination in the image. 206 | The optionally permission mode (*chmod*) can be set as the first parameter. 207 | 208 | ##### `EXTRACT SOURCE DEST` 209 | `EXTRACT` copies a given file or directory from the image to the destination. 210 | 211 | ##### `PATH /my/guest/path` 212 | `PATH` adds the given path to an overlaying PATH variable, used within the `RUN` command. 213 | 214 | ##### `WORKDIR /my/guest/path` 215 | `WORKDIR` sets the working directory within the image. 216 | 217 | ##### `ENV KEY [VALUE]` 218 | `ENV` either sets or unsets an environment variable to be used within the image. 219 | If two parameters are given, the first is the key and the second the value. 220 | If one parameter is given, the environment variable will be removed. 221 | 222 | An environment variable can be either used via `$VAR` within another sub-shell (`sh -c 'echo $VAR'`) or substituted beforehand via `@@VAR@@`. 223 | 224 | ``` 225 | ENV FOO BAR 226 | 227 | RUN sh -c 'echo FOO = $FOO' # FOO = BAR - substituted within a sh in the image 228 | RUN echo FOO = @@FOO@@ # FOO = BAR - substituted beforehand via pimod 229 | 230 | ENV FOO 231 | ``` 232 | 233 | ##### `RUN CMD [PARAMS...]` 234 | `RUN` executes a command in the chrooted image based on QEMU user emulation. 235 | 236 | Caveat: because the Pifile is just a Bash script, pipes do not work as one might suspect. 237 | A possible workaround could be the usage of `bash -c`: 238 | 239 | ``` 240 | RUN bash -c 'hexdump /dev/urandom | head' 241 | ``` 242 | 243 | ##### `HOST CMD [PARAMS...]` 244 | `HOST` executed a command on the local host and can be used to prepare files, cross-compile software, etc. 245 | 246 | #### 4. Postprocess Stage 247 | ##### `ZERO` 248 | `ZERO` fills unused space in the filesystem with zeros. 249 | This allows for better compression of the resulting image, resulting in smaller image files. 250 | Useful when creating images for distribution. 251 | 252 | ### Pifile Extensions 253 | Because the *Pifile* is just a Bash script, some ~~dirty~~ brilliant hacks and extensions are possible. 254 | 255 | #### Bulk execution 256 | Sub shells can be used with the `RUN` command. 257 | 258 | ```sh 259 | RUN sh -c ' 260 | apt-get update 261 | DEBIAN_FRONTEND=noninteractive apt-get -y dist-upgrade 262 | apt-get install -y sl 263 | ' 264 | ``` 265 | 266 | #### Inplace Files 267 | Here documents can also be used to create files inside of the guest system, e.g., by using `tee` or `dd`. 268 | 269 | ```bash 270 | RUN tee /bin/example.sh < /tmp/hello' 17 | RUN chmod +x /tmp/hello 18 | PATH /tmp/ 19 | RUN hello 20 | 21 | # test if INSTALL works 22 | INSTALL "README.md" "/tmp/" 23 | WORKDIR /tmp 24 | RUN head -n1 "README.md" 25 | 26 | # test if EXTRACT works 27 | RUN sh -c 'echo "hello from within the guest" > /tmp/hello' 28 | EXTRACT "/tmp/hello" "./hello" 29 | 30 | # test an INCLUDE - both with and without the .Pifile extension 31 | INCLUDE examples/Module-Hello.Pifile 32 | INCLUDE examples/Module-Hello 33 | 34 | # test a multiline command 35 | RUN sh -c " 36 | echo hello 37 | echo world 38 | " 39 | -------------------------------------------------------------------------------- /examples/RPi-RaspberryPiOSLite.Pifile: -------------------------------------------------------------------------------- 1 | FROM https://downloads.raspberrypi.org/raspios_lite_armhf/images/raspios_lite_armhf-2021-05-28/2021-05-07-raspios-buster-armhf-lite.zip 2 | 3 | PUMP 200M 4 | 5 | RUN apt-get update 6 | RUN apt-get install -y cowsay 7 | 8 | RUN /usr/games/cowsay "huhu pimod~" 9 | 10 | # test a multiline command 11 | RUN bash -c " 12 | echo hello 13 | echo world 14 | " 15 | 16 | # test environment vars via ENV 17 | ENV FOO BAR 18 | RUN sh -c 'echo FOO = $FOO' 19 | RUN echo FOO = @@FOO@@ 20 | 21 | # also test the ENV var on the HOST 22 | HOST sh -c 'echo FOO = $FOO' 23 | HOST echo FOO = @@FOO@@ 24 | 25 | # unset FOO again 26 | ENV FOO 27 | RUN sh -c 'echo FOO = $FOO' 28 | RUN echo FOO = @@FOO@@ 29 | -------------------------------------------------------------------------------- /modules/chroot.sh: -------------------------------------------------------------------------------- 1 | # chroot_setup mounts the given image file and prepares a chroot. 2 | # Usage: chroot_setup PATH_TO_IMAGE 3 | chroot_setup() { 4 | trap 'handle_error ${?} "${@}"' ERR 5 | 6 | LOOP=$(mount_image "${1}") 7 | CHROOT_MOUNT=$(mktemp -d) 8 | 9 | local loop_root="/dev/mapper/${LOOP}p${IMG_ROOT}" 10 | mount "${loop_root}" "${CHROOT_MOUNT}/" 11 | 12 | mount --bind /dev "${CHROOT_MOUNT}/dev" 13 | mount --rbind /sys "${CHROOT_MOUNT}/sys" 14 | mount --bind /proc "${CHROOT_MOUNT}/proc" 15 | mount --bind /dev/pts "${CHROOT_MOUNT}/dev/pts" 16 | 17 | qemu_setup 18 | 19 | # mount additional partitions 20 | chroot "${CHROOT_MOUNT}" mount -a || \ 21 | echo -e "\033[0;33m### Warning: Mounting image partitions using /etc/fstab failed.\033[0m" 22 | } 23 | 24 | # zero_fill fills the unused space in the filesystem with zeros 25 | # Usage: zero_fill 26 | zero_fill() { 27 | local zero_file="${CHROOT_MOUNT}/zero.fill" 28 | 29 | dd if=/dev/zero of=${zero_file} bs=1M status=progress || true 30 | rm ${zero_file} 31 | } 32 | 33 | # chroot_teardown unmounts the given image file, mounted with chroot_setup. 34 | # Usage: chroot_teardown PATH_TO_IMAGE 35 | chroot_teardown() { 36 | # disable script abort on error 37 | set +eE 38 | # ignore further errors 39 | trap "" ERR 40 | 41 | mapfile -t RUNNING < <(lsof -t "${CHROOT_MOUNT}") 42 | if [ "${RUNNING[*]}" ]; then 43 | echo -e "\033[0;33m### Warning: Remaining processes (${RUNNING[*]}) are killed.\033[0m" 44 | kill -9 "${RUNNING[@]}" 45 | fi 46 | unset RUNNING 47 | 48 | qemu_teardown 49 | 50 | i=0 51 | while ! umount -Rflv "${CHROOT_MOUNT}/"; do 52 | if [ $((i=i+1)) -ge 10 ]; then 53 | return 102 54 | fi 55 | sleep 1 56 | done 57 | 58 | rm -r "${CHROOT_MOUNT}" 59 | unset CHROOT_MOUNT 60 | 61 | umount_image "${LOOP}" 62 | unset LOOP 63 | } 64 | 65 | -------------------------------------------------------------------------------- /modules/env.sh: -------------------------------------------------------------------------------- 1 | # ENV_VARS is an associative array of environment variables, set via ENV. 2 | declare -A ENV_VARS=() 3 | 4 | # env_vars_set saves an environment variable mapping. 5 | # Usage: env_vars_set KEY VALUE 6 | env_vars_set() { 7 | ENV_VARS["${1}"]="${2}" 8 | } 9 | 10 | # env_vars_del removes an environment variable mapping. 11 | # Usage: env_vars_del KEY 12 | env_vars_del() { 13 | unset ENV_VARS["${1}"] 14 | } 15 | 16 | # env_vars_export_cmd creates a single "export K1=V1 K2=V2 ...;" output. If no 17 | # values are present, an empty output is generated. 18 | # Usage: env_vars_export_cmd 19 | env_vars_export_cmd() { 20 | if [[ "${#ENV_VARS[@]}" -eq "0" ]]; then 21 | echo "" 22 | return 23 | fi 24 | 25 | declare -a pairs 26 | 27 | for key in "${!ENV_VARS[@]}"; do 28 | pairs+=("${key}=${ENV_VARS["$key"]}") 29 | done 30 | 31 | echo "export ${pairs[*]};" 32 | } 33 | 34 | # env_vars_subst replaces all previously defined environment variables in the 35 | # form of "@@ENV@@" by its value. 36 | # Usage: env_vars_subst echo hello @@USER_NAME@@ 37 | env_vars_subst() { 38 | for part in "${@}"; do 39 | for key in "${!ENV_VARS[@]}"; do 40 | part="${part/"@@${key}@@"/${ENV_VARS["$key"]}}" 41 | done 42 | printf '%s ' "$part" 43 | done 44 | } 45 | -------------------------------------------------------------------------------- /modules/error.sh: -------------------------------------------------------------------------------- 1 | if [ -z "${PIMOD_DEBUG+x}" ]; then 2 | PIMOD_DEBUG=0 3 | fi 4 | 5 | # handle_error handles an error which may occur during Pifile execution. 6 | # Usage: handle_error RETURN_CODE COMMAND 7 | handle_error() { 8 | if [[ -z ${2+x} ]]; then 9 | echo -e "\033[0;31m### Error ${1}, cleaning up...\033[0m" 10 | else 11 | echo -e "\033[0;31m### Error: \"${2}\" returned ${1}, cleaning up...\033[0m" 12 | fi 13 | 14 | if [ "${PIMOD_DEBUG}" -eq 1 ]; then 15 | echo "Running an interactive debug shell, use ^D or 'exit' to cleanup." 16 | $SHELL 17 | fi 18 | 19 | # teardown chroot / mount / loop environment 20 | chroot_teardown 21 | exit "${1}" 22 | } 23 | -------------------------------------------------------------------------------- /modules/esceval.sh: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: 0BSD 2 | # Copyright 2014 Alexander Kozhevnikov 3 | 4 | # On 2019-04-25, this script was compatible with Bourne and POSIX shells. 5 | # EXCEPT for the following exceptions: 6 | # Function declarations (First appeared in SVR2 Bourne shells in 1984). 7 | # command built-in (not supported in really old Bourne shells) 8 | 9 | if command -v esceval 1>/dev/null 2>&1 10 | then 11 | : 12 | else 13 | if (eval 'echo ${A%%a} ${A#a}' 1>/dev/null 2>&1) 14 | then 15 | eval 'esceval() 16 | { 17 | case $# in 0) return 0; esac 18 | ( 19 | while : 20 | do 21 | escaped=\'\'' 22 | unescaped=$1 23 | while : 24 | do 25 | case $unescaped in 26 | *\'\''*) 27 | escaped=$escaped${unescaped%%\'\''*}"'"'\''"'" 28 | unescaped=${unescaped#*\'\''} 29 | ;; 30 | *) 31 | break 32 | esac 33 | done 34 | escaped=$escaped$unescaped\'\'' 35 | shift 36 | case $# in 0) break; esac 37 | printf "%s " "$escaped" || return $? 38 | done 39 | printf "%s\n" "$escaped" 40 | ) 41 | }' 42 | else 43 | esceval() 44 | { 45 | case $# in 0) return 0; esac 46 | ( 47 | b='\\' 48 | while : 49 | do 50 | escaped=` 51 | printf '%s\n' "$1" \ 52 | | sed " 53 | s/'/'$b''/g 54 | 1 s/^/'/ 55 | $ s/$/'/ 56 | " 57 | ` || return $? 58 | shift 59 | case $# in 0) break; esac 60 | printf '%s ' "$escaped" || return $? 61 | done 62 | printf '%s\n' "$escaped" 63 | ) 64 | } 65 | fi 66 | fi -------------------------------------------------------------------------------- /modules/from_remote.sh: -------------------------------------------------------------------------------- 1 | if [ -z "${PIMOD_CACHE+x}" ]; then 2 | PIMOD_CACHE="/var/cache/pimod" 3 | fi 4 | 5 | # from_remote_valid checks if the given string indicates a valid URL. 6 | from_remote_valid() { 7 | local schemeRegexp="^(https?|ftp)://.*" 8 | [[ $1 =~ $schemeRegexp ]] 9 | } 10 | 11 | # unarchive_image extracts files from an image and moves the largest to a given path. 12 | unarchive_image() { 13 | local archive="${1}" 14 | local tmpfile="${2}" 15 | local unzip_image 16 | unzip_dir=$(mktemp -d) 17 | 18 | 7z e -bd -o"${unzip_dir}" "${archive}" 19 | 20 | # pick largest file, as it is most likely the image 21 | # shellcheck disable=SC2012 22 | unzip_image=$(ls -S -1 "${unzip_dir}" | head -n1) 23 | mv "${unzip_dir}/${unzip_image}" "${tmpfile}" 24 | rm -rf "${unzip_dir}" 25 | } 26 | 27 | # from_remote_fetch tries to fetch a remote image and uses it for FROM. 28 | from_remote_fetch() { 29 | local url 30 | local url_path 31 | local download_path 32 | 33 | url="${1}" 34 | url_path=$(echo "${url}" | sed 's/.*:\/\///') 35 | download_path="${PIMOD_CACHE}/${url_path}" 36 | 37 | if [ -f "${download_path}" ]; then 38 | echo "Using cache: ${download_path}" 39 | else 40 | echo "Fetching remote: ${url}" 41 | mkdir -p "$(dirname "${download_path}")" 42 | wget --progress=dot:giga -O "${download_path}" "${url}" || rm "${download_path}" 43 | fi 44 | 45 | local tmpfile 46 | local mime 47 | 48 | tmpfile=$(mktemp -u) 49 | mime=$(file -b --mime-type "${download_path}") 50 | 51 | case "${mime}" in 52 | application/octet-stream) 53 | # let's seriously hope it's an image.. 54 | cp "${download_path}" "${tmpfile}" 55 | ;; 56 | 57 | application/zip) 58 | unarchive_image "${download_path}" "${tmpfile}" 59 | ;; 60 | 61 | application/x-7z-compressed) 62 | unarchive_image "${download_path}" "${tmpfile}" 63 | ;; 64 | 65 | application/gzip) 66 | gunzip -c "${download_path}" > "${tmpfile}" 67 | ;; 68 | 69 | application/x-gzip) 70 | gunzip -c "${download_path}" > "${tmpfile}" 71 | ;; 72 | 73 | application/x-xz) 74 | unxz -c "${download_path}" > "${tmpfile}" 75 | ;; 76 | 77 | *) 78 | echo -e "\033[0;31m### Error: Unknown MIME ${mime}\033[0m" 79 | return 1 80 | ;; 81 | esac 82 | 83 | export SOURCE_IMG="${tmpfile}" 84 | export SOURCE_IMG_TMP=1 85 | } 86 | -------------------------------------------------------------------------------- /modules/mount.sh: -------------------------------------------------------------------------------- 1 | # mount_image mounts the given image file as a loop device and "returns"/prints 2 | # the name of the loop device (e.g. loop0). 3 | # Usage: mount_image PATH_TO_IMAGE 4 | mount_image() { 5 | local loop_path 6 | loop_path=$(losetup -f "${1}" --show) 7 | kpartx -avs "${loop_path}" 1>&2 8 | 9 | basename "${loop_path}" 10 | } 11 | 12 | # umount_image unmounts the given image file, mounted with mount_image. 13 | # Usage: umount_image LOOP_NAME 14 | umount_image() { 15 | i=0 16 | while ! kpartx -dvs "/dev/${1}" 1>&2; do 17 | if [[ $((i=i+1)) -ge 10 ]]; then 18 | return 103 19 | fi 20 | sleep 1 21 | done 22 | 23 | i=0 24 | while ! losetup -d "/dev/${1}" 1>&2; do 25 | if [ $((i=i+1)) -ge 10 ]; then 26 | return 104 27 | fi 28 | sleep 1 29 | done 30 | } 31 | -------------------------------------------------------------------------------- /modules/path.sh: -------------------------------------------------------------------------------- 1 | # GUEST_PATH is an overlay PATH variable used within RUN. Initially, it is 2 | # identical to the PATH variable of the host. 3 | GUEST_PATH="${PATH}" 4 | 5 | # path_add adds a given PATH to the overlay GUEST_PATH variable. 6 | path_add() { 7 | if [[ -z ${GUEST_PATH+x} ]]; then 8 | GUEST_PATH=${1} 9 | else 10 | GUEST_PATH=${GUEST_PATH}:${1} 11 | fi 12 | } 13 | -------------------------------------------------------------------------------- /modules/pifile.sh: -------------------------------------------------------------------------------- 1 | # inspect_pifile_name checks the name of the given Pifile (first parameter) 2 | # and sets the internal DEST_IMG variable to the first part of this filename, 3 | # if the filename has the format of XYZ.Pifile, with XYZ being alphanumeric 4 | # or signs. 5 | # Usage: inspect_pifile_name PIFILE_NAME 6 | inspect_pifile_name() { 7 | local name 8 | name="${1%.Pifile}" 9 | 10 | if [ "${name}" ]; then 11 | DEST_IMG="${name}.img" 12 | else 13 | DEST_IMG="${1}.img" 14 | fi 15 | 16 | export DEST_IMG 17 | } 18 | 19 | # execute_pifile runs the given Pifile. 20 | # Usage: execute_pifile PIFILE 21 | execute_pifile() { 22 | if [[ -z ${1+x} ]] || [[ ! -f "${1}" ]]; then 23 | echo -e "\033[0;31m### Error: Pifile \"${1}\" does not exist.\033[0m" 24 | return 1 25 | fi 26 | 27 | grep -q $'\r' "${1}" && \ 28 | echo -e "\033[0;33m### Warning: Pifile contains CRLF, please use a Unix-like newline.\033[0m" 29 | 30 | inspect_pifile_name "$1" 31 | 32 | bash -n "$1" 33 | 34 | declare -a stages=( "10-setup" "20-prepare" "30-chroot" "40-postprocess" ) 35 | for stage in "${stages[@]}"; do 36 | pushd "$(dirname "$0")/modules" > /dev/null || exit 2 37 | . "../stages/00-commands.sh" 38 | . "../stages/${stage}.sh" 39 | popd > /dev/null || exit 2 40 | 41 | pre_stage 42 | 43 | # shellcheck disable=SC1090 44 | . "$1" 45 | 46 | post_stage 47 | done 48 | } 49 | -------------------------------------------------------------------------------- /modules/qemu.sh: -------------------------------------------------------------------------------- 1 | QEMU_ARCHS="arm armeb aarch64" 2 | 3 | qemu_setup() { 4 | # disable preloading (not working because of missing paths) 5 | [[ -f "${CHROOT_MOUNT}/etc/ld.so.preload" ]] && \ 6 | sed -i 's/^/#/g' "${CHROOT_MOUNT}/etc/ld.so.preload" 7 | 8 | QEMU_MOUNTS=() 9 | 10 | # bind mount 11 | for arch in ${QEMU_ARCHS}; do 12 | # get local paths of qemu 13 | local qemu_path 14 | local bin_path 15 | 16 | qemu_path=$(command -v "qemu-${arch}-static") 17 | bin_path=$(dirname "${qemu_path}") 18 | 19 | # recreate bin folders 20 | mkdir -p "${CHROOT_MOUNT}/${bin_path}" 21 | touch "${CHROOT_MOUNT}/${qemu_path}" 22 | 23 | mount -o ro,bind "${qemu_path}" "${CHROOT_MOUNT}/${qemu_path}" 24 | QEMU_MOUNTS=("${QEMU_MOUNTS[@]}" "${CHROOT_MOUNT}/${qemu_path}") 25 | 26 | # enable arch 27 | update-binfmts --enable "qemu-${arch}" || \ 28 | echo -e "\033[0;33m### Warning: Architecture ${arch} might not be supported.\033[0m" 29 | done 30 | } 31 | 32 | qemu_teardown() { 33 | # unmount qemu binaries, remove temp files 34 | i=0 35 | while ! umount "${QEMU_MOUNTS[@]}"; do 36 | if [ $((i=i+1)) -ge 10 ]; then 37 | return 101 38 | fi 39 | sleep 1 40 | done 41 | 42 | rm "${QEMU_MOUNTS[@]}" 43 | unset QEMU_MOUNTS 44 | 45 | # disable arch 46 | for arch in "${QEMU_ARCHS[@]}"; do 47 | update-binfmts --disable "qemu-${arch}" 48 | done 49 | 50 | # enable preloading libraries 51 | [[ -f "${CHROOT_MOUNT}/etc/ld.so.preload" ]] && \ 52 | sed -i 's/^#//g' "${CHROOT_MOUNT}/etc/ld.so.preload" 53 | 54 | return 0 55 | } 56 | -------------------------------------------------------------------------------- /modules/resolv_conf.sh: -------------------------------------------------------------------------------- 1 | if [ -z "${PIMOD_HOST_RESOLV+x}" ]; then 2 | PIMOD_HOST_RESOLV_TYPE="auto" 3 | fi 4 | 5 | # resolv_conf_setup checks the /etc/resolv.conf file within an image and remaps 6 | # it, if necessary. 7 | resolv_conf_setup() { 8 | local resolv_conf="${CHROOT_MOUNT}/etc/resolv.conf" 9 | 10 | case "${PIMOD_HOST_RESOLV_TYPE}" in 11 | auto) 12 | # Do not mount the host's file when a /etc/resolv.conf already exists. 13 | ((test -f "${resolv_conf}") || (RUN test -e "/etc/resolv.conf")) && return 14 | ;; 15 | 16 | guest) 17 | # Never mount the host's file. 18 | return 19 | ;; 20 | 21 | host) 22 | # Always mount the host's file as an overlay. 23 | ;; 24 | 25 | *) 26 | echo -e "\033[0;31m### Error: unknown resolv type ${PIMOD_HOST_RESOLV_TYPE} \033[0m" 27 | return 1 28 | esac 29 | 30 | if [[ -L "${resolv_conf}" ]]; then 31 | RESOLV_CONF_BACKUP=$(mktemp -u) 32 | mv "${resolv_conf}" "${RESOLV_CONF_BACKUP}" 33 | fi 34 | 35 | if ! touch "${resolv_conf}"; then 36 | echo -e "\033[0;31m### Error: Mounting ${resolv_conf} failed.\033[0m" 37 | return 1 38 | fi 39 | mount -o ro,bind /etc/resolv.conf "${resolv_conf}" 40 | 41 | RESOLVE_MOUNT=1 42 | } 43 | 44 | # resolv_conf_teardown resets the actions done by resolv_conf_setup. 45 | resolv_conf_teardown() { 46 | [[ -z ${RESOLVE_MOUNT+x} ]] && return 47 | 48 | local resolv_conf="${CHROOT_MOUNT}/etc/resolv.conf" 49 | 50 | umount "${resolv_conf}" 51 | 52 | if [[ -n ${RESOLV_CONF_BACKUP+x} ]]; then 53 | mv "${RESOLV_CONF_BACKUP}" "${resolv_conf}" 54 | fi 55 | } 56 | -------------------------------------------------------------------------------- /modules/workdir.sh: -------------------------------------------------------------------------------- 1 | # WORKDIR_PATH is the workdir to be used within the chroot. 2 | WORKDIR_PATH="/" 3 | 4 | # workdir_set overwrites the WORKDIR_PATH with the given parameter. 5 | workdir_set() { 6 | WORKDIR_PATH=${1} 7 | } 8 | -------------------------------------------------------------------------------- /pimod.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euE 4 | 5 | pushd "$(dirname "$0")" > /dev/null 6 | 7 | . ./modules/chroot.sh 8 | . ./modules/env.sh 9 | . ./modules/error.sh 10 | . ./modules/esceval.sh 11 | . ./modules/from_remote.sh 12 | . ./modules/mount.sh 13 | . ./modules/path.sh 14 | . ./modules/pifile.sh 15 | . ./modules/qemu.sh 16 | . ./modules/resolv_conf.sh 17 | . ./modules/workdir.sh 18 | 19 | popd > /dev/null 20 | 21 | show_help() { 22 | cat <> "${DEST_IMG}" 28 | 29 | echo ", +" | sfdisk -N "${IMG_ROOT}" "${DEST_IMG}" 30 | 31 | local loop 32 | loop=$(mount_image "${DEST_IMG}") 33 | 34 | e2fsck -p -f "/dev/mapper/${loop}p${IMG_ROOT}" || ( 35 | ERR="${?}" 36 | if [ "${ERR}" -gt 2 ]; then 37 | echo -e "\033[0;31m### Error: File system repair failed (${ERR}).\033[0m" 38 | return "${ERR}" 39 | fi 40 | ) 41 | resize2fs "/dev/mapper/${loop}p${IMG_ROOT}" 42 | 43 | umount_image "${loop}" 44 | } 45 | 46 | # ADDPART adds a partition of a given size, partition type and file system 47 | # 48 | # Usage: ADDPART SIZE PTYPE FS 49 | ADDPART() { 50 | if [ $# -ne 3 ]; then 51 | echo -e "\033[0;31m### Error: usage ADDPART SIZE PTYPE FS\033[0m" 52 | return 1 53 | fi 54 | 55 | echo -e "\033[0;32m### ADDPART ${1} ${2} ${3}\033[0m" 56 | 57 | if [[ -b "${DEST_IMG}" ]]; then 58 | echo -e "\033[0;31m### Error: Block device ${DEST_IMG} cannot be altered.\033[0m" 59 | return 1 60 | fi 61 | 62 | BS="1M" 63 | 64 | # units does not print to stderr, thus test call before using output 65 | echo -n "addpart conversion to ${BS} * " 66 | units -t "${1}B" "${BS}B" 67 | 68 | COUNT=$(units -t ${1}B ${BS}B) 69 | 70 | # Ceil the number if a decimal is given. 71 | if [[ "${COUNT}" == *.* ]]; then 72 | COUNT=$(( $(echo "${COUNT}" | cut -d. -f1) + 1 )) 73 | fi 74 | 75 | echo "addpart ceil: ${BS} * ${COUNT}" 76 | 77 | # Error on unset PTYPE 78 | if [[ -z ${2+x} ]]; then 79 | echo -e "\033[0;31m### Error: Partition type unspecified, possible options:\033[0m" 80 | sfdisk -T 81 | return 1 82 | fi 83 | 84 | echo "checking mkfs.${3}" 85 | 86 | if ! command -v mkfs.${3}; then 87 | echo -e "\033[0;31m### Error: file system ${3} is not available.\033[0m" 88 | return 1 89 | fi 90 | 91 | dd if=/dev/zero bs="${BS}" count="${COUNT}" >> "${DEST_IMG}" 92 | 93 | local data_part_start 94 | data_part_start=$(( $(sfdisk -l "${DEST_IMG}" -o END -n | tail -n1) + 1 )) 95 | 96 | echo "$data_part_start,+,${2}" | sfdisk -a "${DEST_IMG}" 97 | 98 | local loop 99 | loop=$(mount_image "${DEST_IMG}") 100 | 101 | mkfs.${3} "/dev/mapper/${loop}p$((IMG_ROOT + 1))" 102 | 103 | umount_image "${loop}" 104 | } 105 | -------------------------------------------------------------------------------- /stages/30-chroot.sh: -------------------------------------------------------------------------------- 1 | pre_stage() { 2 | chroot_setup "${DEST_IMG}" "${IMG_ROOT}" 3 | 4 | resolv_conf_setup 5 | } 6 | 7 | # INSTALL installs a given file or directory into the destination in the 8 | # image. The optionally permission mode (chmod) can be set as the first 9 | # parameter. 10 | # 11 | # Usage: INSTALL [MODE] SOURCE DEST 12 | INSTALL() { 13 | echo -e "\033[0;32m### INSTALL $*\033[0m" 14 | 15 | local src="" 16 | local dst="" 17 | 18 | case "$#" in 19 | "2") 20 | src="$1" 21 | dst="$2" 22 | ;; 23 | 24 | "3") 25 | src="$2" 26 | dst="$3" 27 | ;; 28 | 29 | *) 30 | echo -e "\033[0;31m### Error: INSTALL [MODE] SOURCE DEST\033[0m" 31 | return 1 32 | ;; 33 | esac 34 | 35 | if [[ -d "${src}" ]]; then 36 | cp -r -T -P --preserve=mode "${src}" "${CHROOT_MOUNT}/${dst}" 37 | else 38 | cp -r -P --preserve=mode "${src}" "${CHROOT_MOUNT}/${dst}" 39 | fi 40 | 41 | if [[ "$#" -eq "3" ]]; then 42 | chmod "$1" "${CHROOT_MOUNT}/${dst}" 43 | fi 44 | } 45 | 46 | # EXTRACT copies a given file or directory from the image to the destination. 47 | # 48 | # Usage: EXTRACT SOURCE DEST 49 | EXTRACT() { 50 | echo -e "\033[0;32m### EXTRACT $*\033[0m" 51 | 52 | local src="${CHROOT_MOUNT}/$1" 53 | local dst=$2 54 | 55 | if [[ -d "${src}" ]]; then 56 | cp -r -T -P --preserve=mode "${src}" "${dst}" 57 | else 58 | cp -r -P --preserve=mode "${src}" "${dst}" 59 | fi 60 | } 61 | 62 | # PATH adds the given path to an overlaying PATH variable, used within the RUN 63 | # command. 64 | # 65 | # Usage: PATH /my/guest/path 66 | PATH() { 67 | path_add "${1}" 68 | echo -e "\033[0;32m### PATH ${GUEST_PATH}\033[0m" 69 | } 70 | 71 | # WORKDIR sets the working directory within the image. 72 | # 73 | # Usage: WORKDIR /my/guest/path 74 | WORKDIR() { 75 | workdir_set "${1}" 76 | echo -e "\033[0;32m### WORKDIR ${WORKDIR_PATH}\033[0m" 77 | } 78 | 79 | # ENV either sets or unsets an environment variable to be used within the image. 80 | # If two parameters are given, the first is the key and the second the value. 81 | # If one parameter is given, the environment variable will be removed. 82 | # 83 | # An environment variable can be either used via $VAR within another sub-shell 84 | # (sh -c 'echo $VAR') or substituted beforehand via @@VAR@@. 85 | # 86 | # Usage: ENV KEY [VALUE] 87 | ENV() { 88 | local key="" 89 | local value="" 90 | 91 | case "$#" in 92 | "1") 93 | key="$1" 94 | env_vars_del "$key" 95 | ;; 96 | 97 | "2") 98 | key="$1" 99 | value="$2" 100 | env_vars_set "$key" "$value" 101 | ;; 102 | 103 | *) 104 | echo -e "\033[0;31m### Error: ENV KEY [VALUE]\033[0m" 105 | return 1 106 | ;; 107 | esac 108 | 109 | echo -e "\033[0;32m### ENV ${key}=${value}\033[0m" 110 | } 111 | 112 | # RUN executes a command in the chrooted image based on QEMU user emulation. 113 | # 114 | # Caveat: because the Pifile is just a Bash script, pipes do not work as one 115 | # might suspect. A possible workaround could be the usage of `bash -c`: 116 | # > RUN bash -c 'hexdump /dev/urandom | head' 117 | # 118 | # Usage: RUN CMD PARAMS... 119 | RUN() { 120 | echo -e "\033[0;32m### RUN ${*}\033[0m" 121 | 122 | local cmd_esceval="$(esceval "$@")" 123 | local cmd_env_subst="$(env_vars_subst "$cmd_esceval")" 124 | 125 | PATH=${GUEST_PATH} chroot "${CHROOT_MOUNT}" \ 126 | /bin/sh -c "cd ${WORKDIR_PATH}; $(env_vars_export_cmd) ${cmd_env_subst}" 127 | } 128 | 129 | # HOST executed a command on the local host and can be used to prepare files, 130 | # cross-compile software, etc. 131 | # 132 | # Usage: HOST CMD PARAMS... 133 | HOST() { 134 | echo -e "\033[0;32m### HOST ${*}\033[0m" 135 | 136 | local cmd_esceval="$(esceval "$@")" 137 | local cmd_env_subst="$(env_vars_subst "$cmd_esceval")" 138 | 139 | /bin/sh -c "$(env_vars_export_cmd) ${cmd_env_subst}" 140 | } 141 | -------------------------------------------------------------------------------- /stages/40-postprocess.sh: -------------------------------------------------------------------------------- 1 | post_stage() { 2 | resolv_conf_teardown 3 | 4 | chroot_teardown "${DEST_IMG}" 5 | } 6 | 7 | # PUMP increases the image's size about the given amount. 8 | # 9 | # Usage: PUMP SIZE_IN_MB 10 | PUMP() { 11 | echo -e "\033[0;32m### PUMP ${1}; file system usage:\033[0m" 12 | df -h "${CHROOT_MOUNT}" 13 | } 14 | 15 | ZERO() { 16 | echo -e "\033[0;32m### ZERO; filling the filesystem with zeros...\033[0m" 17 | zero_fill 18 | } 19 | --------------------------------------------------------------------------------