feat: Add Chezmoi module (#215)

* build: Added cosign.pub

* Feat: Add chezmoi module

* Excaped characters making build fail

* Switch ` for '

* Fixed syntax issues

* Fixed bash syntax and systemctl parameters

* Update modules/chezmoi/module.yml to correct README url

Co-authored-by: Gerald Pinder <gmpinder@gmail.com>

* Used -z instead of `! -n`

* Fixed default settings

* Made script more verbose to ease debugging

* Fixed wrong default values

* Changed output to be more meaningful

for the end user, instead of the developer.

* Added debugging function

* Rename `install` > `install_chezmoi`, imrpove docs

Gave `install` a clearer name.
Added information to docs and improved readability.

* Fixed conditional check for set variable

* Removed unneeded commands and updated output.

* Change to official public key

* Fixed invalid systemd targets

* Fix chezmoi dir being created but not populated

~/.local/share/chezmoi is created before this service runs, failing `ConditionPathExists=!%h/.local/share/chezmoi`.
`.git` only exists if a repository has been cloned there.

* Made variable naming conform to project style

* fix: Redo suggested commits

I accidently overwrote some commits.

* docs: Explain what lingering does

* docs: fix typo in shortdesc

(oops my fault)

---------

Co-authored-by: Gerald Pinder <gmpinder@gmail.com>
Co-authored-by: xyny <60004820+xynydev@users.noreply.github.com>
This commit is contained in:
exponentactivity 2024-05-06 17:48:39 +02:00 committed by GitHub
parent 3a33f7706b
commit 824fa565e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 241 additions and 0 deletions

67
modules/chezmoi/README.md Normal file
View file

@ -0,0 +1,67 @@
# `chezmoi`
The `chezmoi` module takes care of installing, initializing and updating you dotfiles.
Each feature can be enabled or disabled individually.
Installation of the `chezmoi` binary happens at build time and is done by downloading the `amd64` binary from the latest release to `/usr/bin/chezmoi`.
This can be disabled by setting `install` to false. (defaults: true)
A systemd user service is installed that will initialize a `chezmoi` repository on chezmoi's default path (`~/.local/share/chezmoi`) for any user when it logs in, or at boot if it has lingering enabled.
The service will only run if `~/.local/share/chezmoi` does not exist.
Set `repository` to the URL of your dotfiles repository. (eg. `repository: https://example.org/user/dotfiles`)
:::note
The value of `repository` will be passed directly to `chezmoi init --apply ${repository}`.
See the [`chezmoi init` documenation](https://www.chezmoi.io/reference/commands/init/) for detailed syntax.
:::
Set `disable_init` to `true` if you do not want to install the init service.
:::caution
If `repository` is not set, and `disable_init` is false the module will fail, due to not being able to initialize the repository.
:::
Set `enable_all_users` to `false` if you want to install the update and initialization services, but do not want them enabled for all users.
You can enable them manually instead when the system has been installed:
To enable the services for a single user, run the following command as that user:
```bash
systemctl enable --user chezmoi-init.service chezmoi-update.timer`
```
To manually enable the services for all users, run the following command with sudo:
```bash
sudo systemctl enable --user chesmoi-init.service chezmoi-update.timer
```
To turn on lingering for a given user, run the following commmand with sudo:
:::note
By default, any systemd units in a user's namespace will run after the user logs in, and will close after the user closes their last session.
When you enable lingering for a user, that user's units will run at boot and will continue running even if the user has no active sessions.
If your dotfiles only contain things used by humans, such as cosmetic settings and aliases, you shouldn't need this.
If you understand the above implications, and decide you need this feature, you can enable it with the following command, after installation:
:::
```bash
sudo loginctl enable-linger <username>`
```
You can configure the interval between updates of your dotfiles by setting the value of `run_every`.
The string is passed directly to OnUnitInactiveSec. (default: '1d')
See [`systemd.time` documenation](https://www.freedesktop.org/software/systemd/man/latest/systemd.time.html) for detailed syntax.
Examples: '1d' (1 day - default), '6h' (6 hours), '10m' (10 minutes)
Likewise, `wait_after_boot` configures the delay between the system booting and the update service starting.
This follows the same syntax as `run_every`. (default: '5m')
The installation of the initialization service and the update service can be disabled separately by setting `disable_init` and/or `disable_update` to `true`. (Both default: false)
:::caution
Note that this will skip the installation of the services completely. If you want them installed but disabled, see `enable_all_users` instead.
:::
## Development
Setting `DEBUG=true` inside `chezmoi.sh` will enable additional output in bash useful for debugging.

155
modules/chezmoi/chezmoi.sh Normal file
View file

@ -0,0 +1,155 @@
#!/usr/bin/env bash
# Tell build process to exit if there are any errors.
set -euo pipefail
# '-I=0' makes sure the output isn't indented
# Activates debugging output. This should only be true if you changed it yourself.
# -v = Verbose output. Outputs every line before executing.
# -u = Treat unset variables as errors. Useful for spotting typos.
# -x = Show expanded input for conditional statements.
DEBUG=false
if [[ $DEBUG ]]; then
echo "Running in debug mode. If you didn't enable this yourself, this is a bug."
set -vux
fi
# If true, downloads the chezmoi binary from the latest Github release and moves it to /usr/bin/. (default: true)
INSTALL_CHEZMOI=$(echo "$1" | yq -I=0 ".install-chezmoi") # (boolean)
if [[ -z $INSTALL_CHEZMOI || $INSTALL_CHEZMOI == "null" ]]; then
INSTALL_CHEZMOI=true
fi
# The repository with your chezmoi dotfiles. (default: null)
DOTFILE_REPOSITORY=$(echo "$1" | yq -I=0 ".repository") # (string)
# If true, chezmoi services will be enabled for all logged in users, and users with lingering enabled. (default: true)
# If false, chezmoi services will not be enabled for any users, but can be enabled manually, after installation.
#
# To enable the services for a single user, run the following command as that user:
# 'systemctl enable --user chezmoi-init.service chezmoi-update.timer'
#
# To manually enable the services for all users, run the following command with sudo:
# 'sudo systemctl enable --user chesmoi-init.service chezmoi-update.timer'
#
# To turn on lingering for a given user, run the following commmand with sudo:
# 'sudo loginctl enable-linger <username>'
ENABLE_ALL_USERS=$(echo "$1" | yq -I=0 ".enable-all-users") # (boolean)
if [[ -z $ENABLE_ALL_USERS || $ENABLE_ALL_USERS == "null" ]]; then
ENABLE_ALL_USERS=true
fi
# chezmoi-update.service will run with this interval
# This string is passed on directly to systemd's OnUnitInactiveSec. Complete syntax is described here:
# https://www.freedesktop.org/software/systemd/man/latest/systemd.time.html#
# Examples: '1d' (1 day - default), '6h' (6 hours), '10m' (10 minutes)
RUN_EVERY=$(echo "$1" | yq -I=0 ".run-every") # (string)
if [[ -z $RUN_EVERY || $RUN_EVERY == "null" ]]; then
RUN_EVERY="1d"
fi
# chezmoi-update.service will also run this much time after the system has booted.
# Same syntax as RUN_EVERY (default: '5m')
WAIT_AFTER_BOOT=$(echo "$1" | yq -I=0 ".wait-after-boot") # (string)
if [[ -z $WAIT_AFTER_BOOT || $WAIT_AFTER_BOOT == "null" ]]; then
WAIT_AFTER_BOOT="5m"
fi
# If true, disables automatic initialization of chezmoi if no dotfile directory is found. (default: false)
DISABLE_INIT=$(echo "$1" | yq -I=0 ".disable-init") # (boolean)
if [[ -z $DISABLE_INIT || $DISABLE_INIT == "null" ]]; then
DISABLE_INIT=false
fi
# If true, disables automatic activation of 'chezmoi-update.timer'. (default: false)
DISABLE_UPDATE=$(echo "$1" | yq -I=0 ".disable-update") # (boolean)
if [[ -z $DISABLE_UPDATE || $DISABLE_UPDATE == "null" ]]; then
DISABLE_UPDATE=false
fi
echo "Checking for conflicting arguments"
if [[ (-z $DOTFILE_REPOSITORY || $DOTFILE_REPOSITORY == "null") && $DISABLE_INIT == false ]]; then
echo "ERROR: Invalid Config: 'repository' is not set, but initialization is not disabled."
echo "Set a value for 'repository' or set 'disable_update' to true, if you do not wish to initialize a chezmoi directory using this module"
exit 1
fi
if [[ $INSTALL_CHEZMOI == true ]]; then
echo "Checking if curl is installed and executable at /usr/bin/curl"
if [ -x /usr/bin/curl ]; then
echo "Downloading chezmoi binary from the latest Github release"
/usr/bin/curl -Ls https://github.com/twpayne/chezmoi/releases/latest/download/chezmoi-linux-amd64 -o /usr/bin/chezmoi
echo "Ensuring chezmoi is executable"
/usr/bin/chmod 755 /usr/bin/chezmoi
else
echo "ERROR: curl could not be found in /usr/bin/."
echo "Please make sure curl is installed on the system you are building your image."
exit 1
fi
else
echo "Skipping install of chezmoi binary"
fi
if [[ $DISABLE_INIT == false ]]; then
# Write the service to initialize Chezmoi, and insert the repo url in the file
echo "Writing init service to user unit directory"
cat >>/usr/lib/systemd/user/chezmoi-init.service <<EOF
[Unit]
Description=Initializes Chezmoi if directory is missing
# This service will not execute for a user with an existing chezmoi directory
ConditionPathExists=!%h/.local/share/chezmoi/.git/
[Service]
ExecStart=/usr/bin/chezmoi init --apply ${DOTFILE_REPOSITORY}
Type=oneshot
[Install]
WantedBy=default.target
EOF
else
echo "Skipping install of chezmoi-init.service"
fi
if [[ $DISABLE_UPDATE == false ]]; then
# Write the service and timer to update chezmoi for all logged in users and users with lingering enabled
echo "Writing update service to user unit directory"
cat >>/usr/lib/systemd/user/chezmoi-update.service <<EOF
[Unit]
Description=Chezmoi Update
[Service]
ExecStart=/usr/bin/chezmoi update
Type=oneshot
EOF
echo "Writing update timer to user unit directory"
cat >>/usr/lib/systemd/user/chezmoi-update.timer <<EOF
[Unit]
Description=Timer for Chezmoi Update
# This service will only execute for a user with an existing chezmoi directory
ConditionPathExists=%h/.local/share/chezmoi
[Timer]
OnBootSec=${WAIT_AFTER_BOOT}
OnUnitInactiveSec=${RUN_EVERY}
[Install]
WantedBy=timers.target
EOF
else
echo "Skipping install of chezmoi-update.service"
fi
# Enable services
echo "Checking which services to enable"
if [[ $ENABLE_ALL_USERS == true && $DISABLE_INIT == false && $DISABLE_UPDATE == false ]]; then
echo "Enabling init timer and update service"
systemctl --global enable chezmoi-init.service chezmoi-update.timer
elif [[ $ENABLE_ALL_USERS == true && $DISABLE_INIT == true && $DISABLE_UPDATE == false ]]; then
echo "Enabling update timer"
systemctl --global enable chezmoi-update.timer
elif [[ $ENABLE_ALL_USERS == true && $DISABLE_INIT == false && $DISABLE_UPDATE == true ]]; then
echo "Enabling init service"
systemctl --global enable chezmoi-init.service
else
echo "No services were enabled"
fi

View file

@ -0,0 +1,19 @@
name: chezmoi
shortdesc: The chezmoi module installs the latest chezmoi release at build time, along with services to clone a dotfile repository and keep it up-to-date.
readme: https://raw.githubusercontent.com/blue-build/modules/main/modules/chezmoi/README.md
example: |
type: chezmoi
# Installs chezmoi to /usr/bin/chezmoi from latest Github release
install_chezmoi: true # Optional - Default: true - Expects type: boolean
# () Git repository to initialize
repository: "https://example.org/user/dotfiles" # Required - Default: n/a - Expects type: string
# Enable the modules services globally for all users
enable_all_users: true # Optional - Default: true - Expects type: boolean
# Dotfiles will be updated with this interval
run_every: '1d' # Optional - Default: '1d' - Expects type: string
# Dotfile updates will wait this long after a boot before running
wait_after_boot: '5m' # Optional - Default: '5m' - Expects type: string
# Disable the service that initializes `repository` on users that are logged in or has linger enabled
disable_init: false # Optional - Default: false - Expects type: boolean
# Disable the timer that updates chezmoi with the interval set above
disable_update: false # Optional - Default: false - Expects type: boolean