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:
parent
2c5382f249
commit
4bbd9f0f3b
|
@ -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')
|
Loading…
Reference in New Issue