# Advanced PiBuilder This document explains how to customise PiBuilder to your needs. ## Contents - [Overview](#overview) - [Getting started](#gettingStarted) - [PiBuilder installation options](#generalOptions) - [Per-host installation options](#hostSpecificOptions) - [Environment variable overrides](#envVarOverrides) - [about Git options](#aboutGitOptions) - [Script scaffolding](#scriptScaffolding) - [The PiBuilder Patching System](#patchingSystem) - [How PiBuilder scripts search for files, folders and patches](#scriptSearch) - [Search function](#supportingFileFunction) - [Patch function](#tryPatchFunction) - [File edit function](#tryEditFunction) - [Folder merge function](#tryMergeFunction) - [Preparing your own patches](#patchPreparation) - [Tools overview: *diff* and *patch*](#patchTools) - [Basic process](#patchSummary) - [Configure home directory](#configHome) - [`.bashrc`](#configBashrc) - [`.config/iotstack_backup/config.yml`](#configBackupCfg) - [`.config/rclone/rclone.conf`](#configRcloneCfg) - [`.gitconfig`](#configGit) - [`.gitignore_global`](#configGitIgnore) - [`crontab`](#crontab) - [Existing customisation points](#patchPoints) - [DHCP client daemon](#etc_dhcpcd_conf) - [Docker daemon](#etc_docker_daemon) - [System swap-file](#etc_dphys_swapfile) *deprecated* - [GRUB](#etc_defaults_grub) - [Locales](#etc_locales) - [Network interfaces](#etc_network) - [Network interface monitoring](#etc_rc_local) - [Network Manager customisation](#etc_networkmanager) - [DNS resolver](#etc_resolvconf_conf) - [Samba (SMB)](#etc_samba_smb_conf) - [Secure Shell (SSH)](#etc_ssh) - [Kernel parameters](#etc_sysctl_d) - [Journal control](#etc_systemd_journald_conf) - [Time synchronisation](#etc_systemd_timesyncd_conf) - [Dynamic device management (UDEV)](#etc_udev_rules_d) - [Using your custom branch in a build](#customBuild) - [Original build method still works](#originalBuild) - [Keeping in sync with GitHub](#githubSync) - [Some notes on Virtual Memory](#vmNotes) ## Overview PiBuilder's main goal is to tailor a Raspberry Pi OS system to support IOTstack. If you are a first-time user, running the PiBuilder scripts and (implicitly) accepting all defaults will get you a stable "server" platform optimised for running your Docker containers. As time goes on and you make changes to your Raspberry Pi, you may find yourself wondering what would happen if your Raspberry Pi failed (corrupted SD card; magic smoke; operator error) and you needed to rebuild it. Here's an example of the kind of problem you might encounter. PiBuilder installs [IOTstackBackup](https://github.com/Paraphraser/IOTstackBackup). Suppose you decide to take advantage of that. You follow the IOTstackBackup [README](https://github.com/Paraphraser/IOTstackBackup/blob/master/README.md). You choose the RCLONE option and set up a connection with Dropbox so your backups are saved in the cloud. And you finish off by creating a cron job to run `iotstack_backup` once a day. If you ever have to rebuild your Raspberry Pi from scratch, PiBuilder will still install IOTstackBackup but you won't be able to run `iotstack_restore` to restore your IOTstack as of the last backup. Two other components are required: 1. Your RCLONE configuration. This contains your Dropbox token and is stored in `~/.config/rclone`. 2. Your IOTstackBackup configuration. That tells IOTstackBackup to use RCLONE and Dropbox for backup and restore operations. It is stored in `~/.config/iotstack_backup`. It's actually a chicken-and-egg problem. Those files aren't included in any backup and, even if they were, that wouldn't help because you would still need the configuration files to be in the right place on your Raspberry Pi *before* you could fetch the backup files and extract the configuration files. The solution is to add the [IOTstackBackup](#configBackupCfg) and [RCLONE](#configRcloneCfg) configurations to PiBuilder. Then, the configuration files will already be in the right place at the end of the PiBuilder run and you will be able to run `iotstack_restore` without further ado. Adding those configuration files to PiBuilder also means you won't have to go through the IOTstackBackup setup procedure on the newly-rebuilt Raspberry Pi before you can run `iotstack_backup`. Sort of win, win, win. Customising PiBuilder doesn't just help with IOTstackBackup configuration files. You can include add your own packages to be installed via `apt`. Custom configuration files in `/etc`. Whatever you want, really. ## Getting started The examples here assume you will be working at the command line but you can also use desktop tools. Start by cloning PiBuilder onto your support host: ``` bash $ git clone https://github.com/Paraphraser/PiBuilder.git ~/PiBuilder ``` > PiBuilder does not have to be located in your home directory. It can be anywhere. Just substitute the appropriate path wherever you see `~/PiBuilder`. Create a custom branch to keep your own changes separate from the main repository on GitHub. A custom branch makes it a bit simpler to manage merging if a change you make conflicts with a change coming from GitHub. ``` bash $ cd ~/PiBuilder $ git switch -c custom ``` > You don't have to call your branch "custom". You can choose any name you like. ## PiBuilder installation options Use a text editor to open: ``` ~/PiBuilder/boot/scripts/support/pibuilder/options.sh ``` The file supplied with PiBuilder looks like this: ``` bash # this file is "sourced" in all build scripts. In the release version, # all variables are commented-out and shown with their default values. # - skip full upgrade in the 01 script. #SKIP_FULL_UPGRADE=false # - skip firmware in the 01 script. #SKIP_EEPROM_UPGRADE=false # - preference for kernel. Only applies to 32-bit installations. If # true, adds "arm_64bit=1" to /boot/config.txt #PREFER_64BIT_KERNEL=false # VM_SWAP only applies to (1) a device which self-identifies as a # Raspberry Pi and (2) has the dphys-swapfile utility at the expected # path. This should be true for any Raspberry Pi hardware running # Raspberry Pi OS Bookworm or earlier. These options are supported: # VM_SWAP=disable # turns off swapping. You should consider this on any Pi # that boots from SD. # VM_SWAP=automatic # same as "disable" if the Pi is running from SD. Otherwise, # changes /etc/dphys-swapfile configuration so that swap size # is twice real RAM, with a maximum limit of 2GB. In practice, # this will usually result in 2GB of swap space. You should # consider this if your Pi boots from SSD. # VM_SWAP=custom # applies whatever patching instructions are found in: # ./support/etc/dphys-swapfile.patch # Same as "automatic" but does not check if running from SD. # VM_SWAP=default # the Raspberry Pi OS defaults apply. In practice, this means # swap is enabled and the swap space is 100MB. # if VM_SWAP is not defined but the old DISABLE_VM_SWAP=true then # that combination is interpreted as VM_SWAP=disable #VM_SWAP=automatic # - default language # Whatever you change this to must be in your list of active locales # (set via ~/PiBuilder/boot/scripts/support/etc/locale.gen.patch) #LOCALE_LANG="en_GB.UTF-8" # - Raspberry Pi ribbon-cable camera control # Options are: disabled, "false", "true" and "legacy" #ENABLE_PI_CAMERA=false # - Handling options for .bashrc and .profile # Options are: "append" (default), "replace" and "skip" # See PiBuilder "login" tutorial #DOT_BASHRC_ACTION=append #DOT_PROFILE_ACTION=append ``` The defaults are appropriate for most first-time builds. However, you can uncomment any variable and set its right hand side as follows: * `SKIP_FULL_UPGRADE` to `true`. This prevents the 01 script from performing a "full upgrade". It may be appropriate if you want to test against a base release of Raspberry Pi OS. * `SKIP_EEPROM_UPGRADE` to `true`. This prevents the 01 script from updating your Raspberry Pi's firmware. Otherwise, the 01 script runs: ``` bash $ rpi-eeprom-update ``` If and only if the response includes "UPDATE AVAILABLE" is a firmware update applied. The EEPROM is updated during the reboot at the end of the 01 script. The process adds extra time to the normal reboot cycle so please be patient. * `PREFER_64BIT_KERNEL` to `true`. This only applies to 32-bit versions of Raspbian. The overall effect is a 64-bit kernel with a 32-bit user mode. * `VM_SWAP` is **deprecated**. It will work on a device which self-identifies as a Raspberry Pi and has the `dphys-swapfile` utility installed. This should be true for any Raspberry Pi running Raspberry Pi OS Bookworm or earlier, in which case the following values are supported: - `disable` to disable virtual memory (VM) swapping. This is appropriate if your Raspberry Pi boots from SD **and** has limited RAM. - `automatic`: - If the Pi is running from an SD card, this is the same as `disable`. - If the Pi is not running from an SD card, the script changes the swap configuration in `/etc/dphys-swapfile` so that swap size is calculated in two steps: 1. The amount of real RAM is doubled (eg a 2GB Raspberry Pi 4 will be doubled to 4GB); 2. A maximum limit of 2GB is applied. This calculation will result in a 2GB swap file for any Raspberry Pi with 1GB or more of real RAM. This is the recommended option if your Raspberry Pi boots from SSD or HD. Rules 1 and 2 are implemented by the `./etc/dphys-swapfile.patch` supplied with PiBuilder. If you change or override that file then whatever rules your patch imposes will be implemented by `automatic`. - `custom` is equivalent to `automatic` but it does not check if your system is running from SD. If you want to enable swap on an SD system, this or "default" are the options to use. - `default` makes no changes to the virtual memory system. The current Raspberry Pi OS defaults enable virtual memory swapping with a swap file size of 100MB. This is perfectly workable on systems with 4GB of RAM or more. If `VM_SWAP` is not set, it defaults to `automatic`. Running out of RAM causes swapping to occur and that, in turn, has both a performance penalty (because SD cards are quite slow) and increases the wear and tear on the SD card (leading to a heightened risk of failure). There are two main causes of limited RAM: - Insufficient physical memory. A good example is a Raspberry Pi Zero W2 which only has 512MB to start with; and/or - Expecting your Raspberry Pi to do too much work, such as running a significant number of containers which either have large memory footprints, or cause a lot of I/O and consume cache buffers, or both. If you disable VM swapping by setting `VM_SWAP` to `disable`, but you later decide to re-enable swapping, run these commands: ``` bash $ sudo systemctl enable dphys-swapfile.service $ sudo reboot ``` You can always check if swapping is enabled using the `swapon -s` command. Silence means swapping is disabled. VM swapping is not **bad**. Please don't disable swapping without giving it some thought. If you can afford to add an SSD, you'll get a better result with swapping enabled than if you stick with the SD and disable swapping. * `LOCALE_LANG` to a valid language descriptor but any value you set here **must** also be enabled via a locale patch. See [setting localisation options](./docs/locales.md) tutorial. "en_GB.UTF-8" is the default language and I recommend leaving that enabled in any locale patch that you create. * `ENABLE_PI_CAMERA` controls whether the Raspberry Pi ribbon-cable camera support is enabled at boot time: - `false` (or undefined) means "do not attempt to enable the camera". - `true` means "enable the camera in the mode that is native for the version of Raspberry Pi OS that is running". - `legacy`, if the Raspberry Pi is running: - *Buster*, then `legacy` is identical to `true`; - *Bullseye* the legacy camera system is loaded rather than the native version. In other words, Bullseye's camera system behaves like Buster and earlier. This is the setting to use if downstream applications have not been updated to use Bullseye's native camera system. * `DOT_BASHRC_ACTION` and `DOT_PROFILE_ACTION` both default to `append`. Allowable values if uncommented are `append`, `replace` and `skip`. See [Login Profiles](./docs/login.md) tutorial for more information on how to use these options. ### Per-host installation options Changes you make to the following file apply to **all** your hosts: ``` ~/PiBuilder/boot/scripts/support/pibuilder/options.sh ``` You can also create a variant of the options file which is specific to a given host. You do that by appending `@` followed by the host name. For example, if your Raspberry Pi uses the name "iot-hub", its host-specific options file would be: ``` ~/PiBuilder/boot/scripts/support/pibuilder/options.sh@iot-hub ``` If both a host-specific and a general options file exist, the host-specific file is given precedence and the general file is ignored. ### Environment variable overrides Some of PiBuilder's scripts support additional customisation by setting environment variables that are not listed in the default `options.sh`. You can apply overrides in one of three ways: 1. Adding the environment variable to your `options.sh`; or 2. Specifying the override inline on the call to the script. For example: ``` console $ IOTSTACK="$HOME/MySpecialIOTstack" ./PiBuilder/boot/scripts/03_setup.sh ``` 3. Exporting the override before calling the script. Example: ``` console $ export IOTSTACK="$HOME/MySpecialIOTstack" $ ./PiBuilder/boot/scripts/03_setup.sh ``` The variables supported in this fashion are summarised below. variable | script(s) | default -------------------------|:---------:|------------------------------------------ `IOTSTACK` | 03, 04 | `$HOME/IOTstack` `IOTSTACK_URL` | 03 | `https://github.com/SensorsIot/IOTstack.git` `IOTSTACK_BRANCH` | 03 | `master` `IOTSTACKALIASES_URL` | 03 | `https://github.com/Paraphraser/IOTstackAliases.git` `IOTSTACKALIASES_BRANCH` | 03 | `master` `IOTSTACKBACKUP_URL` | 03 | `https://github.com/Paraphraser/IOTstackBackup.git` `IOTSTACKBACKUP_BRANCH` | 03 | `master` The variables with `_URL` and `_BRANCH` suffixes are intended to make it easy to clone those repositories from your own custom clones, forks and branches. Note: * If you change the `IOTSTACK` variable, you must be consistent and use it for both the 03 and 04 scripts, otherwise PiBuilder will raise an error. ## Script scaffolding Every script has the same basic scaffolding: * source the common functions from `functions.sh` * invoke `run_pibuilder_prolog` which: - sources the [installation options](#generalOptions) from either: - `options.sh@$HOSTNAME` or - `options.sh` - sources a script-specific user-defined prolog, if one exists * perform the installation steps defined in the script * invoke `run_pibuilder_epilog` which sources a script-specific user-defined epilog, if one exists * either reboot your Raspberry Pi or logout, as is appropriate. Note: * When used in the context of shell scripts, the words "*source*", "*sourcing*" and "*sourced*" mean that the associated file is processed, inline, as though it were part of the original calling script. It is analogous to an "include" file. ## The PiBuilder Patching System ### How PiBuilder scripts search for files, folders and patches #### Search function PiBuilder's search function is called `supporting_file()`. Despite the name, it can search for both files and folders. In most cases, `supporting_file()` is used like this: ``` bash TARGET="/etc/resolv.conf" if SOURCE="$(supporting_file "$TARGET")" ; then # do something like copy $SOURCE to $TARGET fi ``` Here's a walkthrough. `supporting_file()` takes a single argument which is always a path beginning with a `/`. The path to the `support` directory is prepended so the argument so you wind up with an absolute path like this: ``` /home/pi/PiBuilder/boot/scripts/support/etc/resolv.conf ``` That path is considered to be the *general* path. A *host-specific* is constructed from the *general* path by appending `@` plus the `$HOSTNAME` environment variable. For example, if `HOSTNAME` had the value "iot-hub" the *host-specific* path would be: ``` /home/pi/PiBuilder/boot/scripts/support/etc/resolv.conf@iot-hub ``` If the *host-specific* path exists, the *general* path is ignored. The *general* path is only used if the *host-specific* path does not exist. If whichever path emerges from the preceding step is: * a file of non-zero length; or * a folder containing at least one **visible** component (file or sub-folder), then `supporting_file()` returns that path and sets its result code to mean that the path can be used. Otherwise the result code is set to mean that no path was found. So, assuming the `if` test succeeds: * `SOURCE` will be the absolute path inside the PiBuilder folder to either a *host-specific* or *general* path containing your customisations; and * `TARGET` will be an absolute path on the Raspberry Pi to the file to be replaced or otherwise manipulated. If the conditional code within the scope of the `if` were, say: ``` cp "$SOURCE" "$TARGET" ``` the effect would be to replace the default version of `resolv.conf` supplied with your Raspberry Pi, with the version provided by you in PiBuilder. #### Patch function The `try_patch()` function takes two or three arguments: 1. A path beginning with a `/` which has the same definition as for [`supporting_file()`](#supportingFileFunction). 2. A comment string summarising the purpose of the patch. 3. An optional boolean. If "true", it instructs the function to ignore patching errors. Defaults to false if omitted. For example: ``` bash try_patch "/etc/resolv.conf" "this is an example" ``` The patch algorithm first checks whether the target (the file to be patched in the running system) actually exists. If it does not then, returns "success" if the third argument is `true`, otherwise returns "fail". If the target exists, the patch algorithm appends `.patch` to the path supplied in the first argument and then invokes `supporting_file()`: ``` bash supporting_file "/etc/resolv.conf.patch" ``` Calling `supporting_file()` implies both *host-specific* and *general* candidates will be considered, with the *host-specific* form given precedence. If `supporting_file()` returns a candidate, the patching algorithm will assume it is a valid patch file and attempt to apply it to the target file. The function sets its result code to mean "success" if either: * the patch was applied successfully; or * the patch failed, in whole or in part, and the third argument is true. Otherwise the function result code is set to mean "fail". The `try_patch()` function has two common use patterns: * unconditional invocation where there are no actions that depend on the success of the patch. For example: ``` bash try_patch "/etc/dhcpcd.conf" "allowinterfaces eth*,wlan*" ``` * conditional invocation where subsequent actions depend on the success of the patch. For example: ``` bash if try_patch "/etc/dphys-swapfile" "setting swap to max(2*physRAM,2048) GB" ; then sudo dphys-swapfile setup fi ``` * conditional invocation where subsequent actions should occur as long as the patch was attempted (the third optional "true" argument). For example: ``` bash if try_patch "/etc/locale.gen" "setting locales (ignore errors)" true ; then sudo dpkg-reconfigure -f noninteractive locales fi ``` #### File edit function The `try_edit()` function takes two arguments: 1. A path beginning with a `/` which has the same definition as for [`supporting_file()`](#supportingFileFunction). 2. A comment string summarising the purpose of the edit. For example: ``` bash try_edit "/etc/dphys-swapfile" "revert swapfile to defaults" ``` The algorithm first checks whether the target (the file to be patched in the running system) actually exists. If it does not then the function returns "fail". If the target exists, the algorithm appends `.sed` to the path supplied in the first argument and then invokes `supporting_file()`: ``` bash supporting_file "/etc/dphys-swapfile.sed" ``` Calling `supporting_file()` implies both *host-specific* and *general* candidates will be considered, with the *host-specific* form given precedence. If `supporting_file()` returns a candidate, the algorithm will assume it is a file containing valid `sed` editing instructions and will attempt to apply it to the target file. The function sets its result code to mean "success" if the editing instructions could be applied and the edited file compares-different from the original. Otherwise the function result code is set to mean "fail". The `try_edit()` function has two common use patterns: * unconditional invocation where there are no actions that depend on the success of the patch. For example: ``` bash try_edit "/etc/dphys-swapfile" "revert swapfile to defaults" ``` * conditional invocation where subsequent actions depend on the success of the patch. For example: ``` bash if try_edit "/etc/dphys-swapfile" "revert swapfile to defaults" ; then sudo dphys-swapfile setup fi ``` #### Folder merge function The `try_merge()` function takes two arguments: 1. A path beginning with a `/` which has the same definition as for [`supporting_file()`](#supportingFileFunction). 2. A comment string summarising the purpose of the merge. For example: ``` bash try_merge "/etc/network" "set up custom interfaces" ``` The merge algorithm invokes `supporting_file()` to see if the source path can be found. Calling `supporting_file()` implies both *host-specific* and *general* candidates will be considered, with the *host-specific* form given precedence. `supporting_file()` will return successfully if the above path exists and is either a file or a non-empty directory. However, `try_merge()` then insists that both the source and target paths lead to *directories*. If both are directories then `rsync` is called to perform a non-overwriting merge. The result code returned by `rsync` becomes the result code returned by `try_merge()`. If any of the preliminary tests fail, `rsync` is not called and the result code is set to indicate failure. The `try_merge()` function has two common use patterns: * unconditional invocation where there are no actions that depend on the success of the merge. For example: ``` bash try_merge "/etc/network" "set up custom interfaces" ``` * conditional invocation where subsequent actions depend on the success of the merge. For example: ``` bash if try_merge "/etc/network" "set up custom interfaces" ; then sudo service networking restart fi ``` ### Preparing your own patches PiBuilder can *apply* patches for you, but you still need to *create* each patch. #### Tools overview: *diff* and *patch* Understanding how patching works will help you to develop and test patches before handing them to PiBuilder. Assume: 1. an «original» file (the original supplied as part of Raspbian); and 2. a «final» file (after your editing to make configuration changes). To create a «patch» file, you use the `diff` tool which is part of Unix: ``` bash $ diff «original» «final» > «patch» ``` Subsequently, given: 1. a fresh Raspbian install where only «original» exists; plus 2. your «patch» file, you use the `patch` tool which is also part of Unix: ``` bash $ patch -bfnz.bak -i «patch» «original» ``` That `patch` command will: 1. copy «original» to «original».bak; and 2. apply «patch» to «original» to convert it to «final». #### Basic process The basic process for creating a patch file for use in PiBuilder is: 1. Make sure you have a baseline version of the file you want to change. The baseline version of a «target» file should always be whatever was in the Raspbian image you downloaded from the web. Typically, there are two situations: * You have run PiBuilder and PiBuilder has already applied a patch to the «target» file. In that case, `«target».bak` is a copy of whatever was in the Raspbian image you downloaded from the web. That means `«target».bak` is your baseline and you don't need to do anything else. * The «target» file has never been changed. The currently-active file is your baseline so you need to preserve it by making a copy before you start changing anything. The most likely place where you will be working is the `/etc` directory so `sudo` is usually appropriate: ``` bash $ sudo cp «target» «target».bak ``` Note: * One of PiBuilder's first actions in the 01 script is to make a copy of `/etc` as `/etc-baseline`. PiBuilder does this before it makes any changes. If you make some changes in the `/etc` directory and only then realise that you forgot to save a baseline copy, you can always fetch a copy of the original file from `/etc-baseline`. 2. Make whatever changes you need to make to the «target». Sometimes this will involve using `sudo` and a text editor. Other times, you will be able to run a configuration tool like `raspi-config` and it will change the «target» file(s) for you. 3. Create a «patch» file using the `diff` tool. For any given patch file, you always have two options: * If the patch file should apply to a **specific** Raspberry Pi, generate the patch file like this: ``` bash $ diff «target».bak «target» > «target».patch@$HOSTNAME ``` * If the patch file should apply to **all** of your Raspberry Pis each time they are built, generate the patch file like this: ``` bash $ diff «target».bak «target» > «target».patch ``` You can do both. A *host-specific* patch always takes precedence over a *general* patch. 4. Place the «patch» file in its proper location in the PiBuilder structure on your support host (Mac/PC). For example, suppose you have prepared a patch that will be applied to the following file on your Raspberry Pi: ``` /etc/resolvconf.conf ``` Remove the file name, leaving the path component: ``` /etc ``` The path to the support folder in your PiBuilder structure on your support host is: ``` ~/PiBuilder/boot/scripts/support ``` Append the path component ("`/etc`") to the path to the support folder: ``` ~/PiBuilder/boot/scripts/support/etc ``` That folder is where your patch files should be placed. The patch file you prepared will have one of the following names: ``` resolvconf.conf.patch@«hostname» resolvconf.conf.patch ``` The proper location for the patch file in the PiBuilder structure structure on your support host is one of the following paths: ``` ~/PiBuilder/boot/scripts/support/etc/resolvconf.conf.patch@«hostname» ~/PiBuilder/boot/scripts/support/etc/resolvconf.conf.patch ``` ### Configure home directory PiBuilder assumes «username» equals "pi". If you choose a different «username», you *might* need to take special care with the following folder and its contents: ``` ~/PiBuilder/boot/scripts/support/home/pi/ ``` This is the default structure: ``` └── home └── pi ├── .bashrc ├── .config │ ├── iotstack_backup │ │ └── config.yml │ └── rclone │ └── rclone.conf ├── .gitconfig ├── .gitignore_global └── crontab ``` Let's suppose that, instead of "pi", you decide to use "me" for your «username». What you *might* need to do is make a copy of the "pi" directory, as in: ``` bash $ cd ~/PiBuilder/boot/scripts/support/home $ cp -a pi me ``` If you have followed the instructions about creating a custom branch to hold your changes, your next step would be: ``` bash $ git add me $ git commit -m "clone default home directory structure" ``` Note: * This duplication is *optional*, not *essential*. If PiBuilder is not able to find a specific home folder for «username», it falls back to using "pi" as the source of files being copied into the `/home/«username»` folder on your Raspberry Pi. #### `.bashrc` The contents of this file are *appended* to the `~/.bashrc` provided automatically by Raspberry Pi OS. The additions: * source [IOTstackAliases](https://github.com/Paraphraser/IOTstackAliases); * enable `DOCKER_BUILDKIT`; and * define `COMPOSE_PROFILES` to be a synonym for `HOSTNAME`. See also [`DOT_BASHRC_ACTION`](#dotLoginAction) which explains how to instruct PiBuilder to *replace* your `.bashrc` with a fully custom file. You can find more information about using compose profiles in [this gist](https://gist.github.com/Paraphraser/eabfedd3f1ac3038dc70a199ef9812de). #### `.config/iotstack_backup/config.yml` This is a placeholder. If you decide to set up [IOTstackBackup](https://github.com/Paraphraser/IOTstackBackup) then you should replace this placeholder with your working configuration. #### `.config/rclone/rclone.conf` This is a placeholder. If you decide to configure [IOTstackBackup](https://github.com/Paraphraser/IOTstackBackup) to use the RCLONE option (eg so your backups are stored in Dropbox), you should replace this placeholder with your working RCLONE configuration. #### `.gitconfig` This is (mostly) a template. At the very least, you should: 1. Replace "Your Name"; and 2. Replace "email@domain.com" If you have not created a key for signing commits, remove the `signingkey` line, otherwise uncomment it and set the correct value. Hint: * You may find it simpler to replace `.gitconfig` with whatever is in `.gitconfig` in your home directory on your support host. You should only need to change `.gitconfig` in PiBuilder if you also change `.gitconfig` your home directory on your support host. Otherwise, the configuration can be re-used for all of your Raspberry Pis. #### `.gitignore_global` This file has a base set of ignore patterns. You can use it as-is or tailor it to your needs. #### `crontab` This is a placeholder containing comments on how to set up cron jobs. PiBuilder will use whatever you supply here to initialise your crontab.