Compare commits

...

29 Commits
1.2.0 ... main

Author SHA1 Message Date
Solène Rapenne 4123ae5a43
Merge pull request #11 from gador/fix-states
also update states.txt in case we use "dry-build"
2024-05-27 11:04:31 +02:00
Florian Brandes c6c481ae26
also update states.txt in case we use "dry-build"
Signed-off-by: Florian Brandes <florian.brandes@posteo.de>
2024-05-26 10:40:45 +02:00
Solène Rapenne 6418bd64ec
bento: use flake-utils to support all systems 2023-06-24 11:46:41 +02:00
Solène Rapenne 05c95104a9
rework README 2023-06-24 11:32:09 +02:00
Solène Rapenne 98361e3555
add a reboot timer 2023-06-24 11:31:49 +02:00
Solène Rapenne 189d476424
Merge pull request #7 from wamserma/patch-1
Update README.md (typo fix)
2023-06-01 23:30:52 +02:00
Solène Rapenne 6c9ae97537
Merge pull request #8 from wamserma/patch-2
bento: typo fix in status message
2023-06-01 23:29:20 +02:00
Markus Wamser 428bd2d374
bento: typo fix in status message 2023-05-30 13:25:12 +02:00
Markus Wamser 56b269cc5c
Update README.md (typo fix) 2023-05-30 12:34:56 +02:00
Solène Rapenne 33fc1e2772
Merge pull request #6 from gador/dry-build
add a config option to not build the config locally
2023-05-04 16:50:57 +02:00
Florian Brandes 3e7125d32e
add a config option to not build the config locally
Signed-off-by: Florian Brandes <florian.brandes@posteo.de>
2023-05-02 21:44:07 +02:00
Solène Rapenne f85a445532
Merge pull request #3 from Blackhole1504/main
Add lib.mkDefault tag to the systemd timer value
2023-01-07 16:18:47 +01:00
Bastien b844a0b203
Make timer the default value
Add lib.mkDefault in front of the timer value to be able to specify per host values
2023-01-07 12:43:56 +01:00
Solène Rapenne 220b3de622
Merge pull request #2 from gador/add-port-setting
add REMOTE_PORT setting
2022-12-26 17:49:37 +01:00
Florian Brandes fcefd9e815
remote_port: bugfix for existing bento configurations
now REMOTE_PORT is set in `bento`, just like TIMEOUT
and therefore optional.

Signed-off-by: Florian Brandes <florian.brandes@posteo.de>
2022-12-20 10:47:58 +01:00
Florian Brandes 6ec3dd7b4e
add REMOTE_PORT setting
this allows the deployment server to run on a different
SSH port than the default port 22.

Signed-off-by: Florian Brandes <florian.brandes@posteo.de>
2022-12-19 19:25:15 +01:00
Solene Rapenne d8c28c8ebd bento: recreate the state file for every rebuild 2022-12-08 00:09:02 +01:00
Solene Rapenne 88f124b5b1 bento: only display two biggest time unit in the elapsed time in status 2022-12-08 00:08:37 +01:00
Solene Rapenne 018880d464 bento: use nvd when available 2022-12-08 00:07:27 +01:00
Solene Rapenne dbfd792609 README: fix a typo 2022-12-08 00:07:14 +01:00
Solene Rapenne be3183d764 README: cleanup DONE tasks 2022-10-06 22:34:44 +02:00
Solene Rapenne 171e9cfe17 lock: bump nixpkgs version 2022-10-06 22:29:56 +02:00
Solene Rapenne fcb949881a bento: status now waits for changes in chroot dir in an infinite loop 2022-10-06 22:29:41 +02:00
Solene Rapenne 2392b7720f utils: use startAt in bento-upgrade service to auto create the timer 2022-10-06 20:52:11 +02:00
Solene Rapenne a8ab5c476d flake: remove version 2022-10-04 23:03:41 +02:00
Solene Rapenne ba692bbf34 doc: fix a typo 2022-10-04 23:03:08 +02:00
Solene Rapenne 6b0eecb7f3 bento: diff shouldn't display an error for new machines 2022-10-04 23:02:59 +02:00
Solene Rapenne 80c0de326b bento: add a new feature to work in a push model 2022-10-04 23:02:41 +02:00
Solene Rapenne 9223913444 bento: remove useless comment 2022-09-29 18:15:02 +02:00
8 changed files with 212 additions and 81 deletions

View File

@ -27,7 +27,7 @@ Bento has a different approach with a "pull" model:
- organized 💼: system administrators have all configurations files in one repository to ease management - organized 💼: system administrators have all configurations files in one repository to ease management
- peace of mind 🧘🏿: configurations can be validated locally by system administrators - peace of mind 🧘🏿: configurations can be validated locally by system administrators
- smart 💡: secrets (arbitrary files) can (soon) be deployed without storing them in the nix store - smart 💡: secrets (arbitrary files) can (soon) be deployed without storing them in the nix store
- robustness in mind 🦾: clients ony need to connect to a remote ssh server, there are many ways to bypass firewalls (corkscrew, VPN, Tor hidden service, I2P, ...) - robustness in mind 🦾: clients only need to connect to a remote ssh server, there are many ways to bypass firewalls (corkscrew, VPN, Tor hidden service, I2P, ...)
- extensible 🧰 🪡: you can change every component, if you prefer using GitHub repositories to fetch configuration files instead of a remote sftp server, you can change it - extensible 🧰 🪡: you can change every component, if you prefer using GitHub repositories to fetch configuration files instead of a remote sftp server, you can change it
- for all NixOS 💻🏭📱: it can be used for anything running NixOS: remote workstations, smartphones or servers in a datacenter - for all NixOS 💻🏭📱: it can be used for anything running NixOS: remote workstations, smartphones or servers in a datacenter
@ -35,7 +35,9 @@ Bento has a different approach with a "pull" model:
This setup need a machine to be online most of the time. NixOS systems (clients) will regularly check for updates on this machine over ssh. This setup need a machine to be online most of the time. NixOS systems (clients) will regularly check for updates on this machine over ssh.
**Bentoo** doesn't necesserarily require a public IP, don't worry, you can use tor hidden service, i2p tunnels, a VPN or whatever floats your boat given it permit to connect to ssh. **Bento** doesn't necesserarily require a public IP, don't worry, you can use tor hidden service, i2p tunnels, a VPN or whatever floats your boat given it permit to connect to ssh.
**Bento** will use `nvd` instead of `nix store diff-closures` if it's available in the `$PATH`.
# How it works # How it works
@ -64,24 +66,15 @@ There is a diagram showing the design pattern of **bento**:
## Major priority ## Major priority
- DONE ~~client should report their current version after an upgrade, we should be able to compute the same value from the config on the server side, this would allow to check if a client is correctly up to date~~ - being able to create a NixOS rootless container that would be used as the chroot server, to avoid reconfiguring the host and use sudo to distribute files
- being able to create a podman compatible NixOS image that would be used as the chroot server, to avoid reconfiguring the host and use sudo to distribute files - a way to allow remote client to update their flakes lock file every time even if no configuration changed, this would be useful to let them stay up to date
- DONE ~~auto rollback like "magicrollback" by deploy-rs in case of losing connectivity after an upgrade~~
- DONE ~~`local_build.sh` and `populate_chroot` should be only one command installed in `$PATH`~~
- DONE ~~upgrades could be triggered by the user by accessing a local socket, like opening a web page in a web browser to trigger it, if it returns output that'd be better~~
- a way to tell a client (when using flakes) to try to update flakes every time even if no configuration changed, to keep them up to date
- DONE ~~being able to use a single flakes with multiple hosts that **bento** will automatically assign to the nixosConfiguration names as hosts~~
- DONE ~~handle automatic reboot if the kernel changed~~
- automatic reboot should be scheduled if desired, this may require making bento a NixOS module to set a timer in it, if no timer then it would reboot immediately
- document config.sh in the reference - document config.sh in the reference
- figure out how to make a tutorial for bento - figure out how to make a tutorial for bento
- DONE ~~sftp timeout should be configurable in `config.sh`~~
- `config.sh` should have variables for the local / remote / both `nixos-rebuild` parameters (useful for `--fallback`) - `config.sh` should have variables for the local / remote / both `nixos-rebuild` parameters (useful for `--fallback`)
## Minor ## Minor
- a systray info widget could tell the user an upgrade has been done - a systray info widget could tell the user an upgrade has been done
- DONE ~~updates should add a log file in the sftp chroot if successful or not~~
- the sftp server could be on another server than the one with the configuration files - the sftp server could be on another server than the one with the configuration files
- provide more useful modules in the utility nix file (automatically use the host as a binary cache for instance) - provide more useful modules in the utility nix file (automatically use the host as a binary cache for instance)
- have a local information how to ssh to the client to ease the rebuild trigger (like a SSH file containing ssh command line) - have a local information how to ssh to the client to ease the rebuild trigger (like a SSH file containing ssh command line)

118
bento
View File

@ -1,11 +1,13 @@
#!/usr/bin/env bash #!/usr/bin/env bash
TIMEOUT=20 TIMEOUT=20
REMOTE_PORT=22
NOLOCALBUILD=0
# FUNCTION LIBRARIES # FUNCTION LIBRARIES
usage() { usage() {
cat <<EOF cat <<EOF
usage: bento init | deploy | diff | build [dry-run|test|switch] | status [delay] | flake-update [input] usage: bento init | deploy | diff | build [dry-run|test|switch] | status | flake-update [input]
bento init bento init
: create the layout for bento in the current directory : create the layout for bento in the current directory
@ -19,8 +21,10 @@ bento diff
bento build [dry-run|test|switch] bento build [dry-run|test|switch]
: build configurations, can activate (test or switch) a build locally : build configurations, can activate (test or switch) a build locally
bento status [delay] bento status
: display information for remote hosts : display information for remote hosts
: if the script isn't run in an interactive terminal, exit after display
: in an interactive terminal, display status and poll for changes to display again
bento flake-update [input] bento flake-update [input]
: recursively update flakes lock files : recursively update flakes lock files
@ -52,7 +56,6 @@ version_diff() {
do do
test -d "${i}" || continue test -d "${i}" || continue
# date calculation
LASTLOG=$(find "${i}/logs/" -type f | sort -n | tail -n 1) LASTLOG=$(find "${i}/logs/" -type f | sort -n | tail -n 1)
LASTLOGVERSION="$(basename "$LASTLOG" | awk -F '_' '{ print $2 }' )" LASTLOGVERSION="$(basename "$LASTLOG" | awk -F '_' '{ print $2 }' )"
EXPECTED_CONFIG="$(awk -F '=' -v host="${i}" 'host == $1 { print $2 }' states.txt)" EXPECTED_CONFIG="$(awk -F '=' -v host="${i}" 'host == $1 { print $2 }' states.txt)"
@ -65,7 +68,14 @@ version_diff() {
if [ ! "${LASTLOGVERSION}" = "${EXPECTED_CONFIG}" ] if [ ! "${LASTLOGVERSION}" = "${EXPECTED_CONFIG}" ]
then then
echo "Changes in $i between ${LASTLOGVERSION} and ${EXPECTED_CONFIG}" echo "Changes in $i between ${LASTLOGVERSION} and ${EXPECTED_CONFIG}"
nix store diff-closures "/nix/store/${LASTLOGVERSION}" "/nix/store/${EXPECTED_CONFIG}"
# better output if nvd is available
if type nvd 2>/dev/null >/dev/null
then
nvd diff "/nix/store/${LASTLOGVERSION}" "/nix/store/${EXPECTED_CONFIG}"
else
nix store diff-closures "/nix/store/${LASTLOGVERSION}" "/nix/store/${EXPECTED_CONFIG}"
fi
else else
echo "$i is running the latest version" echo "$i is running the latest version"
fi fi
@ -228,14 +238,14 @@ cd /var/bento || exit 5
touch .state touch .state
# don't get stuck if we change the host # don't get stuck if we change the host
ssh-keygen -F "${REMOTE_IP}" >/dev/null || ssh-keyscan "${REMOTE_IP}" >> /root/.ssh/known_hosts ssh-keygen -F "${REMOTE_IP}" >/dev/null || ssh-keyscan -p "${REMOTE_PORT}" "${REMOTE_IP}" >> /root/.ssh/known_hosts
STATEFILE="\$(mktemp /tmp/bento-state.XXXXXXXXXXXXXXXX)" STATEFILE="\$(mktemp /tmp/bento-state.XXXXXXXXXXXXXXXX)"
echo "ls -l last_change_date" | sftp -oConnectTimeout="${TIMEOUT}" ${dest}@${REMOTE_IP} >"\${STATEFILE}" echo "ls -l last_change_date" | sftp -oConnectTimeout="${TIMEOUT}" -P "${REMOTE_PORT}" ${dest}@${REMOTE_IP} >"\${STATEFILE}"
if [ "\$?" -ne 0 ] if [ "\$?" -ne 0 ]
then then
echo "There is certainly a network problem with ${REMOTE_IP}" echo "There is certainly a network problem with ${REMOTE_IP} on port ${REMOTE_PORT}"
echo "Aborting" echo "Aborting"
rm "\${STATEFILE}" rm "\${STATEFILE}"
exit 1 exit 1
@ -261,7 +271,7 @@ then
fi fi
else else
echo "update required" echo "update required"
sftp -oConnectTimeout="${TIMEOUT}" ${dest}@${REMOTE_IP}:/config/bootstrap.sh . sftp -oConnectTimeout="${TIMEOUT}" -P "${REMOTE_PORT}" ${dest}@${REMOTE_IP}:/config/bootstrap.sh .
/bin/sh bootstrap.sh /bin/sh bootstrap.sh
echo "\${STATE}" > "/var/bento/.state" echo "\${STATE}" > "/var/bento/.state"
fi fi
@ -274,7 +284,7 @@ EOF
#!/bin/sh #!/bin/sh
# accept the remote ssh fingerprint if not already known # accept the remote ssh fingerprint if not already known
ssh-keygen -F "${REMOTE_IP}" >/dev/null || ssh-keyscan "${REMOTE_IP}" >> /root/.ssh/known_hosts ssh-keygen -F "${REMOTE_IP}" >/dev/null || ssh-keyscan -p "${REMOTE_PORT}" "${REMOTE_IP}" >> /root/.ssh/known_hosts
install -d -o root -g root -m 700 /var/bento install -d -o root -g root -m 700 /var/bento
cd /var/bento || exit 5 cd /var/bento || exit 5
@ -286,7 +296,7 @@ then
find . -maxdepth 1 -type d -exec rm -fr {} \; find . -maxdepth 1 -type d -exec rm -fr {} \;
find . -maxdepth 1 -type f -not -name .state -and -not -name update.sh -and -not -name bootstrap.sh -exec rm {} \; find . -maxdepth 1 -type f -not -name .state -and -not -name update.sh -and -not -name bootstrap.sh -exec rm {} \;
printf "%s\n" "cd config" "get -R ." | sftp -oConnectTimeout="${TIMEOUT}" -r ${dest}@${REMOTE_IP}: printf "%s\n" "cd config" "get -R ." | sftp -oConnectTimeout="${TIMEOUT}" -P "${REMOTE_PORT}" -r ${dest}@${REMOTE_IP}:
# required by flakes # required by flakes
test -d .git || git init test -d .git || git init
@ -351,7 +361,7 @@ fi
# rollback if something is wrong # rollback if something is wrong
# we test connection to the sftp server # we test connection to the sftp server
echo "ls -l last_change_date" | sftp -oConnectTimeout="${TIMEOUT}" ${dest}@${REMOTE_IP} >"\${LOGFILE}" echo "ls -l last_change_date" | sftp -oConnectTimeout="${TIMEOUT}" -P "${REMOTE_PORT}" ${dest}@${REMOTE_IP} >"\${LOGFILE}"
if [ "\$?" -ne 0 ]; if [ "\$?" -ne 0 ];
then then
nixos-rebuild --rollback switch nixos-rebuild --rollback switch
@ -364,9 +374,9 @@ if [ "\$SUCCESS" -eq 0 ]
then then
if [ "\$autoupdate" -eq 1 ] if [ "\$autoupdate" -eq 1 ]
then then
echo "put \${LOGFILE}.gz /logs/\$(date +%Y%m%d-%H%M)_\${OSVERSION}_autoupdate.log.gz" | sftp -oConnectTimeout="${TIMEOUT}" ${dest}@${REMOTE_IP}: echo "put \${LOGFILE}.gz /logs/\$(date +%Y%m%d-%H%M)_\${OSVERSION}_autoupdate.log.gz" | sftp -oConnectTimeout="${TIMEOUT}" -P "${REMOTE_PORT}" ${dest}@${REMOTE_IP}:
else else
echo "put \${LOGFILE}.gz /logs/\$(date +%Y%m%d-%H%M)_\${OSVERSION}_success.log.gz" | sftp -oConnectTimeout="${TIMEOUT}" ${dest}@${REMOTE_IP}: echo "put \${LOGFILE}.gz /logs/\$(date +%Y%m%d-%H%M)_\${OSVERSION}_success.log.gz" | sftp -oConnectTimeout="${TIMEOUT}" -P "${REMOTE_PORT}" ${dest}@${REMOTE_IP}:
fi fi
# handle auto reboot if kernel changed # handle auto reboot if kernel changed
@ -384,9 +394,9 @@ else
# check if we did a rollback # check if we did a rollback
if [ "\$SUCCESS" -eq 255 ] if [ "\$SUCCESS" -eq 255 ]
then then
echo "put \${LOGFILE}.gz /logs/\$(date +%Y%m%d-%H%M)_\${OSVERSION}_rollback.log.gz" | sftp -oConnectTimeout="${TIMEOUT}" ${dest}@${REMOTE_IP}: echo "put \${LOGFILE}.gz /logs/\$(date +%Y%m%d-%H%M)_\${OSVERSION}_rollback.log.gz" | sftp -oConnectTimeout="${TIMEOUT}" -P "${REMOTE_PORT}" ${dest}@${REMOTE_IP}:
else else
echo "put \${LOGFILE}.gz /logs/\$(date +%Y%m%d-%H%M)_\${OSVERSION}_failure.log.gz" | sftp -oConnectTimeout="${TIMEOUT}" ${dest}@${REMOTE_IP}: echo "put \${LOGFILE}.gz /logs/\$(date +%Y%m%d-%H%M)_\${OSVERSION}_failure.log.gz" | sftp -oConnectTimeout="${TIMEOUT}" -P "${REMOTE_PORT}" ${dest}@${REMOTE_IP}:
fi fi
fi fi
rm "\${LOGFILE}.gz" rm "\${LOGFILE}.gz"
@ -420,6 +430,12 @@ build_config()
TMPLOG="$(mktemp /tmp/bento-build-log.XXXXXXXXXXXX)" TMPLOG="$(mktemp /tmp/bento-build-log.XXXXXXXXXXXX)"
rsync -rltgoDL "$SOURCES/" "$TMP/" rsync -rltgoDL "$SOURCES/" "$TMP/"
PARAMS=""
if [ -n "$TARGET_IP" ]
then
PARAMS="--use-remote-sudo --target-host $TARGET_IP"
fi
if [ -z "VERBOSE" ] if [ -z "VERBOSE" ]
then then
output="/dev/null" output="/dev/null"
@ -438,9 +454,9 @@ build_config()
test -d .git || git init >/dev/null 2>/dev/null test -d .git || git init >/dev/null 2>/dev/null
git add . >/dev/null git add . >/dev/null
$SUDO nixos-rebuild "${COMMAND}" --flake ".#${NAME}" | tee "${output}" 2>"${TMPLOG}" >"${TMPLOG}" $SUDO env NIX_SSHOPTS=$NIX_SSHOPTS nixos-rebuild ${PARAMS} "${COMMAND}" --flake ".#${NAME}" | tee "${output}" 2>"${TMPLOG}" >"${TMPLOG}"
else else
$SUDO nixos-rebuild "${COMMAND}" --no-flake -I nixos-config="$TMP/configuration.nix" | tee "${output}" 2>"${TMPLOG}" >"${TMPLOG}" $SUDO env NIX_SSHOPTS=$NIX_SSHOPTS nixos-rebuild ${PARAMS} "${COMMAND}" --no-flake -I nixos-config="$TMP/configuration.nix" | tee "${output}" 2>"${TMPLOG}" >"${TMPLOG}"
fi fi
if [ $? -eq 0 ]; then printf "success " ; else printf "failure " ; BAD_HOSTS="${NAME} ${BAD_HOSTS}" ; SUCCESS=$(( SUCCESS + 1 )) ; cat "${TMPLOG}" ; fi if [ $? -eq 0 ]; then printf "success " ; else printf "failure " ; BAD_HOSTS="${NAME} ${BAD_HOSTS}" ; SUCCESS=$(( SUCCESS + 1 )) ; cat "${TMPLOG}" ; fi
ELAPSED=$(elapsed_time $SECONDS) ELAPSED=$(elapsed_time $SECONDS)
@ -456,6 +472,20 @@ build_config()
sed -i "/^${NAME}/d" "$OLDPWD/../states.txt" >/dev/null sed -i "/^${NAME}/d" "$OLDPWD/../states.txt" >/dev/null
echo "${NAME}=${VERSION}" >> "$OLDPWD/../states.txt" echo "${NAME}=${VERSION}" >> "$OLDPWD/../states.txt"
fi fi
if [ -f "flake.nix" ] && [ "${COMMAND}" = "dry-build" ]
then
# dry-build doesn't create a result link
# redirect stderr of nixos-rebuild and grep for system config
# this should be a fast and inexpensive operation, so we can run it again here
VERSION="$($SUDO nixos-rebuild dry-build --flake ".#${NAME}" 2>&1 >/dev/null | grep "nixos-system-${NAME}" | tr -d '\n' | sed 's,/nix/store/,,' | sed 's,.drv,,')"
if [ ! -z "VERSION" ]
then
touch "${OLDPWD}/../states.txt"
printf " %s" "${VERSION}"
sed -i "/^${NAME}/d" "$OLDPWD/../states.txt" >/dev/null
echo "${NAME}=${VERSION}" >> "$OLDPWD/../states.txt"
fi
fi
echo "" echo ""
cd - >/dev/null || exit 5 cd - >/dev/null || exit 5
@ -509,12 +539,16 @@ deploy_files() {
# only distribute changes if they changed # only distribute changes if they changed
# this avoids bumping the time and trigger a rebuild for nothing # this avoids bumping the time and trigger a rebuild for nothing
diff -r "${STAGING_DIR}/${dest}/config/" "${CHROOT_DIR}/${dest}/config/" >/dev/null diff -r "${STAGING_DIR}/${dest}/config/" "${CHROOT_DIR}/${dest}/config/" >/dev/null 2>/dev/null
CHANGES=$? CHANGES=$?
if [ "$CHANGES" -ne 0 ] if [ "$CHANGES" -ne 0 ]
then then
build_config "${STAGING_DIR}/${dest}/config/" "build" "" "${dest}" if [ $NOLOCALBUILD -eq 1 ]; then
build_config "${STAGING_DIR}/${dest}/config/" "dry-build" "" "${dest}"
else
build_config "${STAGING_DIR}/${dest}/config/" "build" "" "${dest}"
fi
echo " update required" echo " update required"
# copy files in the chroot # copy files in the chroot
install -d -o root -g sftp_users -m 755 "${CHROOT_DIR}" install -d -o root -g sftp_users -m 755 "${CHROOT_DIR}"
@ -547,10 +581,12 @@ elapsed_time() {
SEC=$RAW SEC=$RAW
if [ "$DAYS" -ne 0 ]; then DURATION="${DAYS}d " ; fi ELEMENTS=0
if [ "$HOURS" -ne 0 ]; then DURATION="${DURATION}${HOURS}h " ; fi
if [ "$MINUTES" -ne 0 ]; then DURATION="${DURATION}${MINUTES}m " ; fi if [ "$DAYS" -ne 0 ]; then DURATION="${DAYS}d " ; ELEMENTS=$(( ELEMENTS + 1 )) ; fi
if [ "$SEC" -ne 0 ]; then DURATION="${DURATION}${SEC}s" ; fi if [ "$HOURS" -ne 0 ]; then DURATION="${DURATION}${HOURS}h " ; ELEMENTS=$(( ELEMENTS + 1 )) ; fi
if [ "$ELEMENTS" -lt 2 ] && [ "$MINUTES" -ne 0 ]; then DURATION="${DURATION}${MINUTES}m " ; ELEMENTS=$(( ELEMENTS + 1 )) ; fi
if [ "$ELEMENTS" -lt 2 ] && [ "$SEC" -ne 0 ]; then DURATION="${DURATION}${SEC}s" ; fi
if [ -z "$DURATION" ]; then DURATION="0s" ; fi if [ -z "$DURATION" ]; then DURATION="0s" ; fi
@ -607,7 +643,7 @@ else
MATCH=$(echo "$FLAKES" | awk -v name="${NAME}" 'BEGIN { sum = 0 } name == $1 { sum=sum+1 } END { print sum }') MATCH=$(echo "$FLAKES" | awk -v name="${NAME}" 'BEGIN { sum = 0 } name == $1 { sum=sum+1 } END { print sum }')
if [ "$MATCH" -ne 1 ] if [ "$MATCH" -ne 1 ]
then then
echo "Found ${MATCH} system with this name" echo "Found ${MATCH} systems with this name"
exit 2 exit 2
else else
for flakes in $(find . -name flake.nix) for flakes in $(find . -name flake.nix)
@ -674,10 +710,24 @@ then
test -n "${SINGLE_FLAKE}" && ! [ "$host" = "${SINGLE_FLAKE}" ] && continue test -n "${SINGLE_FLAKE}" && ! [ "$host" = "${SINGLE_FLAKE}" ] && continue
printf "%${PRETTY_OUT_COLUMN}s " "${host}" printf "%${PRETTY_OUT_COLUMN}s " "${host}"
build_config "$i" "$COMMAND" "$SUDO" "$host" build_config "$i" "$COMMAND" "$SUDO" "$host"
# if build succeeded and we used TARGET_IP
# populate the sftp directory with the new version
if [ "$?" -eq 0 ] && [ -n "$TARGET_IP" ]
then
deploy_files "$i" "${host}" "${host}"
fi
done done
else else
printf "%${PRETTY_OUT_COLUMN}s " "${i}" printf "%${PRETTY_OUT_COLUMN}s " "${i}"
build_config "$i" "$COMMAND" "$SUDO" "$i" build_config "$i" "$COMMAND" "$SUDO" "$i"
# if build succeeded and we used TARGET_IP
# populate the sftp directory with the new version
if [ "$?" -eq 0 ] && [ -n "$TARGET_IP" ]
then
deploy_files "$i" "$i"
fi
fi fi
done done
exit 0 exit 0
@ -708,13 +758,17 @@ then
deploy_files "$i" "$i" deploy_files "$i" "$i"
fi fi
# the states files is used for the status function
# we need to update it after each rebuild so if a
# system updates while other configurations are building
# the status will be correct
if [ -f ../states.txt ]
then
cp ../states.txt "${CHROOT_DIR}/states.txt"
fi
done done
# the states files is used for the status function
if [ -f ../states.txt ]
then
cp ../states.txt "${CHROOT_DIR}/states.txt"
fi
exit 0 exit 0
fi fi
@ -749,12 +803,14 @@ then
. ./config.sh . ./config.sh
cd hosts || exit 5 cd hosts || exit 5
if [ -n "$2" ] # if stdout is interactive
# wait for changes and loop
if [ -t 1 ]
then then
while true while true
do do
display_status display_status
sleep "$2" inotifywait -q -e 'modify,create' "${CHROOT_DIR}/states.txt" "${CHROOT_DIR}"/*/logs/ >/dev/null 2>/dev/null
done done
else else
display_status display_status

View File

@ -6,3 +6,8 @@ REMOTE_IP=myserver.example
# maxium time waiting for the SFTP connection to connect # maxium time waiting for the SFTP connection to connect
# default value is 20 # default value is 20
#TIMEOUT=20 #TIMEOUT=20
# port to connect to the remote server
# default is 22
#REMOTE_PORT=22
# don't build locally
#NOLOCALBUILD=1

View File

@ -57,7 +57,7 @@ Here are the steps to add a server named `kikimora` to bento:
1. generate a ssh-key on `kikimora` for root user 1. generate a ssh-key on `kikimora` for root user
2. add kikimora's public key to bento `fleet.nix` file 2. add kikimora's public key to bento `fleet.nix` file
3. reconfigure the ssh host to allow kikimora's key (it should include the `fleet.nix` file) 3. reconfigure the ssh host to allow kikimora's key (it should include the `fleet.nix` file)
4. copy kikimora's config (usually `/etc/nixos/` in bento `hosts/kikimora/` directory 4. copy kikimora's config (usually `/etc/nixos/`) in bento `hosts/kikimora/` directory
5. add utils/bento.nix to its config (in `hosts/kikimora` run `ln -s ../../utils .` and add `./utils/bento.nix` in `imports` list) 5. add utils/bento.nix to its config (in `hosts/kikimora` run `ln -s ../../utils .` and add `./utils/bento.nix` in `imports` list)
6. check kikimora's config locally with `bento build dry-build`, you can check only `kikimora` with `env NAME=kikimora bento build dry-build` 6. check kikimora's config locally with `bento build dry-build`, you can check only `kikimora` with `env NAME=kikimora bento build dry-build`
7. populate the chroot with `sudo bento deploy` to copy the files in `/home/chroot/kikimora/config/` 7. populate the chroot with `sudo bento deploy` to copy the files in `/home/chroot/kikimora/config/`
@ -78,7 +78,9 @@ If you don't want to wait for the timer, you can ssh into the machine to run `sy
As each host is sending a log upon rebuild to tell if it failed or succeeded, the files are used to check what happened since the sftp file `last_time_changed` was created. As each host is sending a log upon rebuild to tell if it failed or succeeded, the files are used to check what happened since the sftp file `last_time_changed` was created.
Using `bento status` you can track the current state of each hosts (time since last update, current NixOS version, status report) Using `bento status` you can track the current state of each hosts (time since last update, current NixOS version, status report).
Bento will display the current state of the fleet, and wait for a change in the chroot directory to display the status again.
[![asciicast](https://asciinema.org/a/520504.svg)](https://asciinema.org/a/520504) [![asciicast](https://asciinema.org/a/520504.svg)](https://asciinema.org/a/520504)
@ -115,3 +117,17 @@ source: +701.9 KiB
systemsettings: +62.6 KiB systemsettings: +62.6 KiB
------------- -------------
``` ```
# Push a configuration to a remote system
It's possible to use `bento` in a *push* model using `TARGET_IP`:
```
env TARGET_IP=10.43.43.1 NAME=myserver bento build switch
```
If the remote system is using a non-standard port, you need to define the according ssh option with `NIX_SSHOPTS`:
```
env NIX_SSHOPTS="-p2222" TARGET_IP=10.43.43.1 NAME=laptop bento build switch
```

View File

@ -11,9 +11,12 @@
- `bento build [dry-run|build|test|switch]` - `bento build [dry-run|build|test|switch]`
- dry-build or build configurations. Using `test` or `switch`, can be used to apply a configuration locally. Default is `build`. - dry-build or build configurations. Using `test` or `switch`, can be used to apply a configuration locally. Default is `build`.
- when using `TARGET_IP`, the command is run on a remote server
- `bento status [delay]` - `bento status`
- display information for remote hosts, if `delay` is set, loop infinitely to display the status every `delay` seconds. Default delay is `0` and doesn't loop. - display information for remote hosts
- if the command is run in interactive mode, `bento` will run in an inifite loop the status display and wait for a change in the chroot directory
- if the command isn't run in interactive mode, the status is only displayed once and `bento` exits
- `bento flake-update [input]` - `bento flake-update [input]`
- recursively update flakes lock files - recursively update flakes lock files
@ -28,6 +31,7 @@ A local file `config.sh` is required for Bento, it contains variable used to gen
- `CHROOT_DIR`: the directory in which the SFTP server will be configured to serve files from - `CHROOT_DIR`: the directory in which the SFTP server will be configured to serve files from
- `REMOTE_IP`: the IP address or hostname used by SFTP client to reach the server with the configuration files - `REMOTE_IP`: the IP address or hostname used by SFTP client to reach the server with the configuration files
- `REMOTE_PORT`: the port of the IP address or hostname used by SFTP client to reach the server with the configuration files. Defaults to 22.
- `TIMEOUT`: time in seconds to wait until aborting when connecting to the SFTP server. Default value is `20` seconds. - `TIMEOUT`: time in seconds to wait until aborting when connecting to the SFTP server. Default value is `20` seconds.
# Environment variables # Environment variables
@ -36,6 +40,8 @@ A local file `config.sh` is required for Bento, it contains variable used to gen
- `BENTO_DIR`: contains the path of a bento directory, so you can run `bento` commands from anywhere - `BENTO_DIR`: contains the path of a bento directory, so you can run `bento` commands from anywhere
- `NAME`: contains machine names (flake config or directory in `hosts/`) to restrict commands `deploy`, `diff` and `build` to this machine only - `NAME`: contains machine names (flake config or directory in `hosts/`) to restrict commands `deploy`, `diff` and `build` to this machine only
- `VERBOSE`: if defined to anything, display `nixos-rebuild` output for local builds done with `bento build` or `bento deploy` - `VERBOSE`: if defined to anything, display `nixos-rebuild` output for local builds done with `bento build` or `bento deploy`
- `TARGET_IP`: can be used to push a configuration closure to a remote system, it's wrapping `nixos-rebuild`. Only work with `bento build [switch|test]`.
- `NIX_SSHOPTS`: parameters for ssh to be used when `TARGET_IP` is used. Example: `NIX_SSHOPTS=-p2222` to use port 2222 instead of the standard port 22.
# Self update mode # Self update mode
@ -49,7 +55,9 @@ This adds at least 8 kB of inbound bandwidth for each input when checking for ch
# Auto reboot # Auto reboot
You can create a file named `REBOOT` in a host directory. When that host will rebuild the system, it will look at the new kernel, kernel modules and initrd, if they changed, a reboot will occur immediately after reporting a successful upgrade. A kexec is used for UEFI systems for a faster reboot (this avoids BIOS and bootloader steps). You can create a file named `REBOOT` in a host directory. When that host will rebuild the system, it will look at the new kernel, kernel modules and initrd, if they changed, a reboot will occur immediately after reporting a successful upgrade. A kexec is used for UEFI systems for a faster reboot (this avoids BIOS and bootloader steps).
Independently of the `REBOOT` file, in the provided `utils/bento.nix`, there is a systemd service that can be enabled to automatically reboot at a given systemd calendar if the kernel/mdoules/initrd changed. This is more convenient for servers.
# Status report of the fleet # Status report of the fleet

View File

@ -1,12 +1,30 @@
{ {
"nodes": { "nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1687171271,
"narHash": "sha256-BJlq+ozK2B1sJDQXS3tzJM5a+oVZmi1q0FlBK/Xqv7M=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "abfb11bd1aec8ced1c9bb9adfe68018230f4fb3c",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1662099760, "lastModified": 1664883812,
"narHash": "sha256-MdZLCTJPeHi/9fg6R9fiunyDwP3XHJqDd51zWWz9px0=", "narHash": "sha256-wqBAcVRBxls2nVaNeQaOy9SRg/bvEUiD26TQDprIg8U=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "67e45078141102f45eff1589a831aeaa3182b41e", "rev": "fe76645aaf2fac3baaa2813fd0089930689c53b5",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -18,8 +36,24 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
} }
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",

View File

@ -2,26 +2,36 @@
description = "bento: an asynchronous NixOS deployment tool"; description = "bento: an asynchronous NixOS deployment tool";
inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-22.05; inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-22.05;
inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = { self, nixpkgs }: { outputs = {
self,
nixpkgs,
flake-utils,
}:
flake-utils.lib.eachDefaultSystem (
system: let
pkgs = nixpkgs.legacyPackages.${system};
in {
packages.default = pkgs.stdenv.mkDerivation {
name = "bento";
src = self;
packages.x86_64-linux.default = patchPhase = ''
with import nixpkgs { system = "x86_64-linux"; }; substituteInPlace bento --replace 'inotifywait' "${pkgs.inotify-tools}/bin/inotifywait";
'';
stdenv.mkDerivation { installPhase = ''
name = "bento-1.0.5"; mkdir -p $out/bin
src = self; mkdir -p $out/share
install -Dm555 bento $out/bin/
installPhase = '' install -Dm444 fleet.nix $out/share/
mkdir -p $out/bin install -Dm444 config.sh.sample $out/share/
mkdir -p $out/share install -Dm444 LICENSE $out/share/
install -Dm555 bento $out/bin/ install -Dm444 README.md $out/share/
install -Dm444 fleet.nix $out/share/ install -Dm444 utils/bento.nix $out/share/
install -Dm444 config.sh.sample $out/share/ '';
install -Dm444 LICENSE $out/share/ };
install -Dm444 README.md $out/share/ }
install -Dm444 utils/bento.nix $out/share/ );
'';
};
};
} }

View File

@ -5,18 +5,9 @@
}: let }: let
timer = "*:0/15"; timer = "*:0/15";
in { in {
systemd.timers.bento-upgrade = {
enable = true;
timerConfig = {
OnCalendar = "${timer}";
Unit = "bento-upgrade.service";
};
wantedBy = ["timers.target"];
after = ["network-online.target"];
};
systemd.services.bento-upgrade = { systemd.services.bento-upgrade = {
enable = true; enable = true;
startAt = lib.mkDefault "${timer}";
path = with pkgs; [openssh git nixos-rebuild nix gzip]; path = with pkgs; [openssh git nixos-rebuild nix gzip];
serviceConfig.Type = "oneshot"; serviceConfig.Type = "oneshot";
script = '' script = ''
@ -26,6 +17,24 @@ in {
restartIfChanged = false; restartIfChanged = false;
}; };
systemd.services.bento-reboot = {
# this is disabled by default
# to avoid wrong expectations from users
enable = false;
startAt = "04:00";
path = with pkgs; [coreutils systemd];
serviceConfig.Type = "oneshot";
script = ''
booted="$(readlink /run/booted-system/{initrd,kernel,kernel-modules})"
built="$(readlink /nix/var/nix/profiles/system/{initrd,kernel,kernel-modules})"
if [ ! "$booted" = "$built" ]
then
systemctl kexec || systemctl reboot
fi
'';
};
systemd.sockets.listen-update = { systemd.sockets.listen-update = {
enable = true; enable = true;
wantedBy = ["sockets.target"]; wantedBy = ["sockets.target"];