diff --git a/modules/environment/login/login-inner.nix b/modules/environment/login/login-inner.nix index ff5ee593..7f452ec3 100644 --- a/modules/environment/login/login-inner.nix +++ b/modules/environment/login/login-inner.nix @@ -16,6 +16,12 @@ writeText "login-inner" '' echo "If nothing works, open an issue at https://github.com/t184256/nix-on-droid/issues or try the rescue shell." fi + ${lib.optionalString config.supervisord.enable '' + (if [ ! -e "${config.supervisord.socketPath}" ]; then + ${config.supervisord.package}/bin/supervisord -c /etc/supervisord.conf || echo "Warning: supervisord failed to start" + fi&) + ''} + ${lib.optionalString config.build.initialBuild '' if [ -e /etc/UNINTIALISED ]; then export HOME="${config.user.home}" diff --git a/modules/module-list.nix b/modules/module-list.nix index a99d4f89..1d9ec4de 100644 --- a/modules/module-list.nix +++ b/modules/module-list.nix @@ -16,6 +16,8 @@ ./environment/shell.nix ./home-manager.nix ./nixpkgs/options.nix + ./services/openssh.nix + ./supervisord.nix ./terminal.nix ./time.nix ./upgrade.nix diff --git a/modules/services/openssh.nix b/modules/services/openssh.nix new file mode 100644 index 00000000..0e9d1a15 --- /dev/null +++ b/modules/services/openssh.nix @@ -0,0 +1,141 @@ +# Copyright (c) 2019-2022, see AUTHORS. Licensed under MIT License, see LICENSE. + +# Parts from nixpkgs/nixos/modules/services/networking/ssh/sshd.nix +# MIT Licensed. Copyright (c) 2003-2022 Eelco Dolstra and the Nixpkgs/NixOS contributors + +{ pkgs, lib, config, ... }: +let + inherit (lib) types; + + cfg = config.services.openssh; + + uncheckedConf = '' + ${lib.concatMapStrings (port: '' + Port ${toString port} + '') cfg.ports} + PasswordAuthentication no + ${lib.flip lib.concatMapStrings cfg.hostKeys (k: '' + HostKey ${k.path} + '')} + ${lib.optionalString cfg.allowSFTP '' + Subsystem sftp ${cfg.package}/libexec/sftp-server + ''} + SetEnv PATH=${config.user.home}/.nix-profile/bin:/usr/bin:/bin + ${cfg.extraConfig} + ''; + + sshdConf = pkgs.runCommand "sshd.conf-validated" { + nativeBuildInputs = [ cfg.package ]; + } '' + cat >$out <&2 + + ${lib.flip lib.concatMapStrings cfg.hostKeys (k: '' + if ! [ -s "${k.path}" ]; then + if ! [ -h "${k.path}" ]; then + rm -f "${k.path}" + fi + mkdir -m 0755 -p "$(dirname '${k.path}')" + ssh-keygen \ + -t "${k.type}" \ + ${if k ? bits then "-b ${toString k.bits}" else ""} \ + ${if k ? rounds then "-a ${toString k.rounds}" else ""} \ + ${if k ? comment then "-C '${k.comment}'" else ""} \ + ${if k ? openSSHFormat && k.openSSHFormat then "-o" else ""} \ + -f "${k.path}" \ + -N "" + fi + '')} + + exec ${cfg.package}/bin/sshd -D -f /etc/ssh/sshd_config + ''; + }; + }; +} diff --git a/modules/supervisord.nix b/modules/supervisord.nix new file mode 100644 index 00000000..913b5004 --- /dev/null +++ b/modules/supervisord.nix @@ -0,0 +1,209 @@ +# Copyright (c) 2019-2022, see AUTHORS. Licensed under MIT License, see LICENSE. + +{ pkgs, lib, config, ... }: +let + inherit (lib) types; + + cfg = config.supervisord; + + format = pkgs.formats.ini {}; + + programType = types.submodule ({ name, config, ... }: { + options = { + enable = lib.mkOption { + description = '' + Whether to enable this program. + ''; + type = types.bool; + default = true; + }; + command = lib.mkOption { + description = '' + The command that will be run as the service's main process. + ''; + type = types.str; + }; + script = lib.mkOption { + description = '' + Shell commands executed as the service's main process. + ''; + type = types.lines; + default = ""; + }; + path = lib.mkOption { + description = '' + Packages added to the service's PATH environment variable. + ''; + type = types.listOf (types.either types.package types.str); + default = []; + }; + autostart = lib.mkOption { + description = '' + Whether to automatically start the process. + + If false, the process has to be manually started using + `supervisorctl`. + ''; + type = types.bool; + default = true; + }; + autoRestart = lib.mkOption { + description = '' + Whether to automatically restart the process if it exits. + + If `unexpected`, the process will be restarted if it exits + with an exit code not listed in the programs's `exitcodes` + configuration. + ''; + type = types.either types.bool (types.enum [ "unexpected" ]); + default = "unexpected"; + }; + environment = lib.mkOption { + description = '' + Environment variables passed to the service's process. + ''; + type = types.attrsOf types.str; + default = {}; + }; + extraConfig = lib.mkOption { + description = '' + Extra structured configurations to add to the [program:x] section. + ''; + type = types.attrsOf (types.either types.str types.bool); + default = {}; + }; + }; + config = { + command = lib.mkIf (config.script != "") + (toString (pkgs.writeShellScript "${name}-script.sh" '' + set -e + ${config.script} + '')); + + environment.PATH = lib.mkDefault (lib.makeBinPath config.path); + }; + }); + + renderAtom = val: + if builtins.isBool val then if val then "true" else "false" + else toString val; + + renderProgram = program: let + section = { + inherit (program) command autostart; + autorestart = program.autoRestart; + environment = let + # FIXME: Make more robust + escape = s: builtins.replaceStrings [ "%" ] [ "%%" ] s; + envs = lib.mapAttrsToList (k: v: "${k}=\"${escape v}\"") program.environment; + in builtins.concatStringsSep "," envs; + } // program.extraConfig; + in lib.mapAttrs (_: v: renderAtom v) section; + + numPrograms = builtins.length (builtins.attrNames enabledPrograms); + enabledPrograms = lib.filterAttrs (_: program: program.enable) cfg.programs; + + structuredConfig = { + supervisord = { + logfile = cfg.logPath; + pidfile = cfg.pidPath; + }; + supervisorctl = { + serverurl = "unix://${cfg.socketPath}"; + }; + unix_http_server = { + file = cfg.socketPath; + }; + "rpcinterface:supervisor" = { + "supervisor.rpcinterface_factory" = "supervisor.rpcinterface:make_main_rpcinterface"; + }; + } // (lib.mapAttrs' (k: v: { + name = "program:${k}"; + value = renderProgram v; + }) enabledPrograms); + + configFile = format.generate "supervisord.conf" structuredConfig; + + # Only expose the "supervisorctl" executable + supervisorctl = pkgs.runCommand "supervisorctl" {} '' + mkdir -p $out/bin + ln -s ${cfg.package}/bin/supervisorctl $out/bin/supervisorctl + ''; +in { + options = { + supervisord = { + enable = lib.mkOption { + description = '' + Whether to enable the supervisord process control system. + + This allows you to define long-running services in Nix-on-Droid. + ''; + type = types.bool; + default = numPrograms != 0; + }; + package = lib.mkOption { + description = '' + The supervisord package to use. + ''; + type = types.package; + default = pkgs.python3Packages.supervisor; + defaultText = lib.literalExpression "pkgs.python3Packages.supervisor"; + }; + socketPath = lib.mkOption { + description = '' + Path to the UNIX domain socket on which supervisord will listen on. + ''; + type = types.path; + default = "/tmp/supervisor.sock"; + }; + pidPath = lib.mkOption { + description = '' + Path to the file in which supervisord saves its PID. + ''; + type = types.path; + default = "/tmp/supervisor.pid"; + }; + logPath = lib.mkOption { + description = '' + Path to the log file. + ''; + type = types.path; + default = "/tmp/supervisor.log"; + }; + programs = lib.mkOption { + description = '' + Definition of supervisord programs. + + Upstream documentations are available at . + ''; + type = types.attrsOf programType; + default = {}; + }; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = lib.flatten (lib.mapAttrsToList (name: program: let + envAsserts = lib.mapAttrsToList (k: v: { + assertion = !(lib.hasInfix "\"" v); + message = "supervisord.programs.${name}.environment.${k}: Value cannot have double quotes at the moment (${v})"; + }) program.environment; + in envAsserts) cfg.programs); + + environment.etc."supervisord.conf" = { + source = configFile; + }; + + environment.packages = [ supervisorctl ]; + + build.activationAfter.reloadSupervisord = '' + if [ ! -e "${config.supervisord.socketPath}" ]; then + echo "Starting supervisord..." + $DRY_RUN_CMD ${cfg.package}/bin/supervisord -c /etc/supervisord.conf + else + echo "Reloading supervisord..." + $DRY_RUN_CMD ${cfg.package}/bin/supervisorctl -c /etc/supervisord.conf update + fi + ''; + }; +}