diff --git a/contrib/start-guix-daemon.py b/contrib/start-guix-daemon.py new file mode 100755 index 0000000..61addcc --- /dev/null +++ b/contrib/start-guix-daemon.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2024 Denis 'GNUtoo' Carikli +# +# 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 . + +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')