Use paths specified from environment variables for /etc configuration files

Some configuration files that need updates are directly under in /etc. To
update them atomically, we need write access to /etc. For Ubuntu Core this is
an issue as /etc is not writable. Only a selection of subdirectories can be
writable. The general solution is symlinks or bind mounts to writable places.
But for atomic writes in /etc, that does not work. So Ubuntu has had a patch
for that that did not age well.

Instead we would like to introduce some environment variables for alternate
paths.

 * SYSTEMD_ETC_HOSTNAME: /etc/hostname
 * SYSTEMD_ETC_MACHINE_INFO: /etc/machine-info
 * SYSTEMD_ETC_LOCALTIME: /etc/localtime
 * SYSTEMD_ETC_LOCALE_CONF: /etc/locale.conf
 * SYSTEMD_ETC_VCONSOLE_CONF: /etc/vconsole.conf
 * SYSTEMD_ETC_ADJTIME: /etc/adjtime

While it is for now expected that there is a symlink from the standard, we
still try to read them from that alternate path. This is important for
`/etc/localtime`, which is a symlink, so we cannot have an indirect symlink or
bind mount for it.

Since machine-id is typically written only once and not updated. This commit
does not cover it. An initrd can properly create it and bind mount it.
This commit is contained in:
Valentin David
2025-06-23 15:32:11 +02:00
committed by Lennart Poettering
parent fa34fda2eb
commit 0dc39dffbd
19 changed files with 376 additions and 48 deletions
+28
View File
@@ -33,6 +33,34 @@ timedatectl set-time "$future_time"
while test ! -f /tmp/clock-changed ; do sleep .5 ; done
mkdir -p /etc/alternate-path
rm -f /etc/alternate-path/localtime
cat <<EOF >/run/systemd/system.conf
[Manager]
ManagerEnvironment=SYSTEMD_ETC_LOCALTIME=/etc/alternate-path/localtime
EOF
mkdir -p /run/systemd/system/systemd-timedated.service.d
cat >/run/systemd/system/systemd-timedated.service.d/override.conf <<EOF
[Service]
Environment=SYSTEMD_ETC_LOCALTIME=/run/alternate-path/mylocaltime
Environment=SYSTEMD_ETC_ADJTIME=/run/alternate-path/myadjtime
EOF
systemctl daemon-reload
systemd-run --on-timezone-change touch /tmp/timezone-changed-alternate-path-1
timedatectl set-timezone Europe/Berlin
while test ! -f /tmp/timezone-changed-alternate-path-1 ; do sleep .5 ; done
systemd-run --on-timezone-change touch /tmp/timezone-changed-alternate-path-2
timedatectl set-timezone Europe/Kyiv
while test ! -f /tmp/timezone-changed-alternate-path-2 ; do sleep .5 ; done
rm /run/systemd/system.conf /run/systemd/system/systemd-timedated.service.d/override.conf
systemctl daemon-reload
systemd-analyze log-level info
touch /testok
+44
View File
@@ -403,6 +403,50 @@ EOF
rm -f /run/systemd/network/ntp99.*
}
teardown_timedated_alternate_paths() {
set +eu
rm -rf /run/systemd/system/systemd-timedated.service.d
systemctl daemon-reload
systemctl restart systemd-timedated
}
testcase_timedated_alternate_paths() {
trap teardown_timedated_alternate_paths RETURN
mkdir -p /run/alternate-path
mkdir -p /run/systemd/system/systemd-timedated.service.d
cat >/run/systemd/system/systemd-timedated.service.d/override.conf <<EOF
[Service]
Environment=SYSTEMD_ETC_LOCALTIME=/run/alternate-path/mylocaltime
Environment=SYSTEMD_ETC_ADJTIME=/run/alternate-path/myadjtime
EOF
systemctl daemon-reload
systemctl restart systemd-timedated
assert_in "Local time:" "$(timedatectl --no-pager)"
assert_eq "$(timedatectl --no-pager set-timezone Europe/Kyiv 2>&1)" ""
assert_eq "$(readlink /run/alternate-path/mylocaltime | sed 's#^.*zoneinfo/##')" "Europe/Kyiv"
assert_in "Time zone: Europe/Kyiv \(EES*T, \+0[0-9]00\)" "$(timedatectl)"
# Restart to force using get_timezine
systemctl restart systemd-timedated
assert_in "Time zone: Europe/Kyiv \(EES*T, \+0[0-9]00\)" "$(timedatectl)"
assert_in "RTC in local TZ: no" "$(timedatectl --no-pager)"
assert_rc 0 timedatectl set-local-rtc 1
assert_in "RTC in local TZ: yes" "$(timedatectl --no-pager)"
assert_eq "$(cat /run/alternate-path/myadjtime)" "0.0 0 0
0
LOCAL"
assert_rc 0 timedatectl set-local-rtc 0
if [[ -e /run/alternate-path/myadjtime ]]; then
echo "/run/alternate-path/myadjtime still exists" >&2
exit 1
fi
}
run_testcases
touch /testok
+39
View File
@@ -278,6 +278,45 @@ test_wildcard() {
hostnamectl set-hostname "$SAVED"
}
teardown_hostnamed_alternate_paths() {
set +eu
rm -rf /run/systemd/system/systemd-hostnamed.service.d
systemctl daemon-reload
systemctl restart systemd-hostnamed
if [[ -f /etc/hostname ]]; then
orig=$(cat /etc/hostname)
if [[ -n "${orig}" ]]; then
hostnamectl hostname "${orig}"
fi
fi
}
testcase_hostnamed_alternate_paths() {
trap teardown_hostnamed_alternate_paths RETURN
mkdir -p /run/alternate-path
mkdir -p /run/systemd/system/systemd-hostnamed.service.d
cat >/run/systemd/system/systemd-hostnamed.service.d/override.conf <<EOF
[Service]
Environment=SYSTEMD_ETC_HOSTNAME=/run/alternate-path/myhostname
Environment=SYSTEMD_ETC_MACHINE_INFO=/run/alternate-path/mymachine-info
EOF
systemctl daemon-reload
systemctl restart systemd-hostnamed
assert_rc 0 hostnamectl set-hostname heisenberg
assert_rc 0 hostnamectl chassis watch
output=$(hostnamectl)
assert_in "Static hostname: heisenberg" "$output"
assert_in "Chassis: watch" "$output"
assert_in "heisenberg" "$(cat /run/alternate-path/myhostname)"
assert_in "CHASSIS=watch" "$(cat /run/alternate-path/mymachine-info)"
}
run_testcases
touch /testok
+47
View File
@@ -651,6 +651,53 @@ testcase_locale_gen_leading_space() {
localectl set-locale en_US.UTF-8
}
teardown_localed_alternate_paths() {
set +eu
rm -rf /run/systemd/system/systemd-localed.service.d
systemctl daemon-reload
systemctl restart systemd-localed
}
testcase_localed_alternate_paths() {
trap teardown_localed_alternate_paths RETURN
mkdir -p /run/alternate-path
mkdir -p /run/systemd/system/systemd-localed.service.d
cat >/run/systemd/system/systemd-localed.service.d/override.conf <<EOF
[Service]
Environment=SYSTEMD_ETC_LOCALE_CONF=/run/alternate-path/mylocale.conf
Environment=SYSTEMD_ETC_VCONSOLE_CONF=/run/alternate-path/myvconsole.conf
EOF
systemctl daemon-reload
systemctl restart systemd-localed
if localectl list-locales | grep "^de_DE.UTF-8$"; then
assert_rc 0 localectl set-locale "LANG=de_DE.UTF-8" "LC_CTYPE=C"
else
skip_locale=1
fi
if localectl list-keymaps | grep -F "^no$"; then
assert_rc 0 localectl set-keymap "no"
else
skip_keymap=1
fi
output=$(localectl)
if [[ -z "${skip_locale-}" ]]; then
assert_in "System Locale: LANG=de_DE.UTF-8" "$output"
assert_in "LANG=de_DE.UTF-8" "$(cat /run/alternate-path/mylocale.conf)"
fi
if [[ -z "${skip_keymap-}" ]]; then
assert_in "VC Keymap: no" "$output"
assert_in "KEYMAP=no" "$(cat /run/alternate-path/myvconsole.conf)"
fi
}
# Make sure the content of kbd-model-map is the one that the tests expect
# regardless of the version installed on the distro where the testsuite is
# running on.
+37
View File
@@ -18,6 +18,10 @@ at_exit() {
rm -fr "$ROOT"
fi
if [[ -d /etc/otherpath ]]; then
rm -rf /etc/otherpath
fi
restore_locale
}
@@ -282,3 +286,36 @@ rm -fv "$ROOT/etc/passwd" "$ROOT/etc/shadow"
(! systemd-firstboot --root="$ROOT" --root-shell=/bin/nonexistentshell)
(! systemd-firstboot --root="$ROOT" --machine-id=invalidmachineid)
(! systemd-firstboot --root="$ROOT" --timezone=Foo/Bar)
mkdir -p "${ROOT}/etc/otherpath"
mkdir -p /etc/otherpath
echo "KEYMAP=us" >/etc/otherpath/vconsole.conf
echo "LANG=en_US.UTF-8" >/etc/otherpath/locale.conf
ln -s "../$(readlink /etc/localtime)" /etc/otherpath/localtime
SYSTEMD_ETC_LOCALE_CONF=/etc/otherpath/locale.conf \
SYSTEMD_ETC_VCONSOLE_CONF=/etc/otherpath/vconsole.conf \
SYSTEMD_ETC_LOCALTIME=/etc/otherpath/localtime \
SYSTEMD_ETC_HOSTNAME=/etc/otherpath/hostname \
systemd-firstboot --root="$ROOT" --copy-locale --copy-keymap --copy-timezone --hostname="weirdpaths"
diff "${ROOT}/etc/otherpath/locale.conf" "/etc/otherpath/locale.conf"
diff "${ROOT}/etc/otherpath/vconsole.conf" "/etc/otherpath/vconsole.conf"
grep -q "weirdpaths" "${ROOT}/etc/otherpath/hostname"
[[ "$(readlink /etc/otherpath/localtime)" = "$(readlink "${ROOT}/etc/otherpath/localtime")" ]]
SYSTEMD_ETC_LOCALE_CONF=/etc/otherpath/locale.conf \
SYSTEMD_ETC_VCONSOLE_CONF=/etc/otherpath/vconsole.conf \
SYSTEMD_ETC_LOCALTIME=/etc/otherpath/localtime \
SYSTEMD_ETC_HOSTNAME=/etc/otherpath/hostname \
systemd-firstboot --root="$ROOT" --force \
--hostname="weirdpaths2" \
--locale=no_NO.UTF-8 \
--keymap=no \
--timezone=Europe/Oslo
grep -q "LANG=no_NO.UTF-8" "${ROOT}/etc/otherpath/locale.conf"
grep -q "KEYMAP=no" "${ROOT}/etc/otherpath/vconsole.conf"
grep -q "weirdpaths2" "${ROOT}/etc/otherpath/hostname"
[[ "$(readlink "${ROOT}/etc/otherpath/localtime")" = "../../usr/share/zoneinfo/Europe/Oslo" ]]