Skip to content

Commit

Permalink
installer: add a naive systemd service state backup and restore
Browse files Browse the repository at this point in the history
Add support for backing up and restoring explicitly disabled and enabled
services by checking their symlinks and restoring or removing them when
applying the backup.

It does naive enabled/disabled services heuristic:

- if the .preset does not say enabled, and there is a symlink in
  multi-user.target.wants, recreate the symink on restore
- if the .preset says enabled, and there was no symlink in
  multi-user.target.wants, remove the symlink on restore

Additionally, it only checks certain services:
- Skip any oneshot services (these are expected to be disabled with
  enabled preset after first boot)
- Skip any services not targeting multi-user

It does support parameterized services though, as supported by frr (via
frr@something).

This allows e.g. docker to continue to be enabled after an update to a
newer version. It should now also keep the choice of baseboxd vs ryu on
upgrade (untested though).

Signed-off-by: Jonas Gorski <[email protected]>
  • Loading branch information
KanjiMonster authored and jklare committed Feb 6, 2025
1 parent 8ea7729 commit d99b28f
Showing 1 changed file with 120 additions and 2 deletions.
122 changes: 120 additions & 2 deletions scripts/installer/lib/backup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,69 @@ parse_file() {
done < $1
}

backup_systemd_state() {
local enabled_services disabled_services service_files service_links
local service enabled preset oneshot

if [ -d "$1/lib/systemd/system" ]; then
service_files=$(ls $1/lib/systemd/system)
fi
if [ -d "$1/etc/systemd/system/multi-user.target.wants" ]; then
service_links=$(ls $1/etc/systemd/system/multi-user.target.wants)
fi

# collect explicitly disabled services
for service in $service_files; do
case "$service" in
*@.service)
# always disabled by default
;;
*.service)
preset=$(grep "$service" $1/lib/systemd/system-preset/*.preset | cut -d ':' -f 2 | cut -d ' ' -f 1)
# check only services enabled by default
[ "$preset" = "enable" ] || continue

wantedby=$(grep "^WantedBy=" $1/lib/systemd/system/$service || true)

# skip oneshots, these got automatically disabled
grep -q "^Type=oneshot" $1/lib/systemd/system/$service && continue

# for now we only support multi-user services
[ "$wantedby" = "WantedBy=multi-user.target" ] || continue

# check for symlink in multi-user.target.wants
enabled=$(find $1/etc/systemd/system/multi-user.target.wants/ -name $service)

# nothing to do if still enabled
[ -z "$enabled" ] || continue

[ -n "$DEBUG" ] && echo "DEBUG: service disabled by user: $service"
disabled_services="$disabled_services ${service%.service}"
;;
esac
done

# collect explicitly enabled services
for service in $service_links; do
case "$service" in
*.service)
service_file=$(readlink $1/etc/systemd/system/multi-user.target.wants/$service)
base=$(basename $service_file)
preset=$(grep "$base" $1/lib/systemd/system-preset/*.preset | cut -d ':' -f 2 | cut -d ' ' -f 1)

# check only services disabled by default
[ "$preset" != "enable" ] || continue

[ -n "$DEBUG" ] && echo "DEBUG: service enabled by user: $service"
enabled_services="$enabled_services ${service%.service}"
;;
esac
done

echo "$disabled_services" > $2/.SERVICES_DISABLED
echo "$enabled_services" > $2/.SERVICES_ENABLED
}

# $1 src $2 dst
apply_fixups() {
# releases pre 4.7.0 are missing gshadow in the backup list
Expand Down Expand Up @@ -150,20 +213,75 @@ create_backup()
parse_file "$1/$USER_BACKUP_FILE" "-" $1 $2
fi

# step 3 - backup changed systemd service states
backup_systemd_state $1 $2

apply_fixups $1 $2

# step 3 - check if anything is left
# step 4 - check if anything is left
[ -n "$(find $2 -type f)" ] || return 0

# step 4 - remove empty directories
# step 5 - remove empty directories
find $2 -depth -type d -exec rmdir -p --ignore-fail-on-non-empty {} \;

DO_RESTORE=true
}

bash_systemctl() {
local dst=$1
local action=$2
local service=$3
local base_service=${service/@*/@}

if [ "$action" = "enable" ]; then
# check that the service exists in the target system
if [ ! -f $dst/lib/systemd/system/$base_service.service ]; then
echo "WARNING: cannot enable service $service.service: $base_service.service not found" >&2
else
# for any services to enable, create symlinks
[ -n "$DEBUG" ] && echo "DEBUG: enabling service: $service.service"
ln -fs /lib/systemd/system/$base_service.service \
$dst/etc/systemd/system/multi-user.target.wants/$service.service
fi
elif [ "$action" = "disable" ]; then
# for any services to disable, remove their symlinks
[ -n "$DEBUG" ] && echo "DEBUG: disabling service: $service.service"
rm -f $dst/etc/systemd/system/multi-user.target.wants/$service.service
else
echo "WARNING: unknown action $action for service $service" >&2
fi
}

restore_systemd_state()
{
local backup=$1
local dst=$2
local enabled_services disabled_service service baseservice

if [ -f "$backup/.SERVICES_ENABLED" ]; then
enabled_services=$(cat $backup/.SERVICES_ENABLED)
fi
if [ -f "$backup/.SERVICES_DISABLED" ]; then
disabled_services=$(cat $backup/.SERVICES_DISABLED)
fi

for service in $enabled_services; do
bash_systemctl "$dst" "enable" "$service"
done

for service in $disabled_services; do
bash_systemctl "$dst" "disable" "$service"
done

rm -f $1/.SERVICES_ENABLED $2/.SERVICES_DISABLED
}

# $1 backup storage dir $2 restore target
restore_backup()
{
# restore systemd services state
restore_systemd_state $1 $2

# rename existing target files if different
for file in $(find $1 -type f); do
basefile="${file#${1}/}"
Expand Down

0 comments on commit d99b28f

Please sign in to comment.