Add contrib script to start the guix-daemon in chroots of systemd distros.

It can be handy to build GNU Boot in a chroot because Guix's
debootstrap can easily debootstrap both PureOS byzantium and Trisquel
10 (nabia), and once done users can simply chroot inside the target
rootfs. In addition chroots also don't have much isolation with the
host, so it is easy to set it up in a way that export /dev/kvm for
faster testing.

The downside is that while some init systems can start daemons while
in chroot, systemd chose not to support that as the separation between
the chroot and the host operating system is not good enough to prevent
accidental modifications of the host system[1].

So practically speaking if we want to start guix-daemon, 'systemctl
start' detects that it's in a chroot and refuses to work.

The concerns of systemd about running some init in chroots[1] is valid
however here we limit the risk by only running the daemon start
commands and not something else that kills host processes.

Also we choose to parse systemd units instead of running the commands
manually as some settings need to be retrieved from the distribution
such as the environment or the build group being used (this varries
accross distributions or installation methods).

[1]https://0pointer.de/blog/projects/changing-roots

Signed-off-by: Denis 'GNUtoo' Carikli <GNUtoo@cyberdimension.org>
neox: fixed whitespace issue in code and fixed commit message
Acked-by: Adrien Bourmault <neox@gnu.org>
This commit is contained in:
Denis 'GNUtoo' Carikli 2024-08-16 03:25:49 +02:00 committed by Adrien Bourmault
parent 2c5382f249
commit 4bbd9f0f3b
Signed by: neox
GPG Key ID: 57BC26A3687116F6
1 changed files with 117 additions and 0 deletions

117
contrib/start-guix-daemon.py Executable file
View File

@ -0,0 +1,117 @@
#!/usr/bin/env python3
#
# Copyright (C) 2024 Denis 'GNUtoo' Carikli <GNUtoo@cyberdimension.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import configparser
import os
import subprocess
import sys
def get_environment(env, string):
left = None
right = None
sep = string.rfind('=')
start = string[:sep].rfind(' ')
left = string[start + 1:sep]
if sep + 1 == len(string):
right = None
else:
if left[0] == '"':
left = string[start + 2:sep]
right = string[sep + 1:string[start + 2:sep].rfind('"') ]
elif left[0] == "'":
left = string[start + 2:sep]
right = string[sep + 1:string[start + 2:sep].rfind("'") ]
else:
right = string[sep + 1:]
if right[0] == "'" and right[-1] == "'":
right = right[1:-1]
elif right[0] == '"' and right[-1] == '"':
right = right[1:-1]
env[left] = right
if start > 0:
get_environment(env, string[:start])
return env
def test_get_environment():
env1 = get_environment({}, """ONE='one' "TWO='two two' too" THREE=""")
env2 = {'ONE': 'one',
'TWO': "'two two' too",
'THREE': None}
k1 = sorted(env1.keys())
k2 = sorted(env2.keys())
assert(k1 == k2)
for k in env1.keys():
assert(env1[k] == env2[k])
test_get_environment()
def run_systemd_service_unit(binary_name, service_file_path):
print("Starting {}.".format(binary_name))
result = subprocess.run(["pidof", "-c", binary_name])
if result.returncode == 0:
return
config = configparser.ConfigParser(strict=False)
config.read(service_file_path)
# man 5 systemd.service has "Unless Type= is oneshot, exactly one
# command must be given." about ExecStart=
# if config.get('Service', 'Type'):
# print("Invalid /lib/systemd/system/guix-daemon.service file.")
# sys.exit(os.EX_OSFILE)
command = config.get('Service', 'ExecStart')
if config.get('Service', 'Environment', fallback=False):
subprocess.run([command + " & "],
shell=True,
capture_output=True,
env=get_environment({}, config.get('Service', 'Environment')))
def mount_store_ro():
mounts = open('/proc/mounts', 'r').readlines()
for mount in mounts:
# Trust that the kernel will always use spaces not to break userspace.
fields = mount.split(" ")
target = fields[1]
options = fields[3]
if target == '/gnu/store':
if 'ro' not in options:
subprocess.run(["mount", "-o", "bind,ro", "/gnu/store", "/gnu/store"])
if 'ID=guix\n' in open('/etc/os-release', 'r').readlines():
print("This script is not meant to run on a guix system.")
print("It's meant for running Guix on foreign distro (in a chroot).")
sys.exit(os.EX_OSFILE)
if os.geteuid() != 0:
print("This script can only run as root.")
sys.exit(os.EX_NOPERM)
mount_store_ro()
run_systemd_service_unit('nscd', '/lib/systemd/system/nscd.service')
run_systemd_service_unit('guix-daemon', '/lib/systemd/system/guix-daemon.service')