-
Notifications
You must be signed in to change notification settings - Fork 109
modules/supervisord: init, modules/openssh: init #203
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
dc61ae1
3a02d09
88b5731
a00dc02
7834088
16107f5
86248da
4d93571
3278a9c
269f6d1
6e9389c
0faa47c
bd44a10
c39cb39
42b95bd
06d245c
5ea968c
f861f45
b416b58
2f92f24
5278b3d
edb3247
82c42b3
56f4e44
ca13b29
01a7447
9222c03
ba1526a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, ... }: | ||
zhaofengli marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 <<EOL | ||
${uncheckedConf} | ||
EOL | ||
|
||
ssh-keygen -q -f mock-hostkey -N "" | ||
sshd -t -f $out -h mock-hostkey | ||
''; | ||
in { | ||
options = { | ||
services.openssh = { | ||
zhaofengli marked this conversation as resolved.
Show resolved
Hide resolved
|
||
enable = lib.mkOption { | ||
description = '' | ||
Whether to enable the OpenSSH secure shell daemon, which | ||
allows secure remote logins. | ||
''; | ||
type = types.bool; | ||
default = false; | ||
}; | ||
autostart = lib.mkOption { | ||
description = '' | ||
Whether to automatically start the OpenSSH daemon. | ||
|
||
If false, the server has to be manually started using | ||
`supervisorctl`. | ||
''; | ||
type = types.bool; | ||
default = true; | ||
}; | ||
package = lib.mkOption { | ||
description = '' | ||
The package to use for OpenSSH. | ||
''; | ||
type = types.package; | ||
default = pkgs.openssh; | ||
defaultText = lib.literalExpression "pkgs.openssh"; | ||
}; | ||
ports = lib.mkOption { | ||
description = '' | ||
Specifies on which ports the SSH daemon listens. | ||
''; | ||
type = types.listOf types.port; | ||
default = [ 8022 ]; | ||
}; | ||
allowSFTP = lib.mkOption { | ||
description = '' | ||
Whether to enable the SFTP subsystem in the SSH daemon. This | ||
enables the use of commands such as {command}`sftp` and | ||
{command}`sshfs`. | ||
''; | ||
type = types.bool; | ||
default = true; | ||
}; | ||
hostKeys = lib.mkOption { | ||
description = '' | ||
Nix-on-Droid can automatically generate SSH host keys. This option | ||
specifies the path, type and size of each key. See | ||
{manpage}`ssh-keygen(1)` for supported types | ||
and sizes. | ||
''; | ||
type = types.listOf types.attrs; | ||
default = | ||
[ { type = "rsa"; bits = 4096; path = "/etc/ssh/ssh_host_rsa_key"; } | ||
{ type = "ed25519"; path = "/etc/ssh/ssh_host_ed25519_key"; } | ||
]; | ||
example = | ||
[ { type = "rsa"; bits = 4096; path = "/etc/ssh/ssh_host_rsa_key"; rounds = 100; openSSHFormat = true; } | ||
{ type = "ed25519"; path = "/etc/ssh/ssh_host_ed25519_key"; rounds = 100; comment = "key comment"; } | ||
]; | ||
}; | ||
extraConfig = lib.mkOption { | ||
description = "Verbatim contents of {file}`sshd_config`."; | ||
type = types.lines; | ||
default = ""; | ||
}; | ||
}; | ||
}; | ||
config = lib.mkIf cfg.enable { | ||
environment.etc = { | ||
"ssh/sshd_config".source = sshdConf; | ||
"ssh/moduli".source = "${cfg.package}/etc/ssh/moduli"; | ||
}; | ||
|
||
supervisord.programs.sshd = { | ||
inherit (cfg) autostart; | ||
path = [ cfg.package pkgs.coreutils ]; | ||
autoRestart = true; | ||
script = '' | ||
Gerschtli marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# don't write to stdout | ||
exec >&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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should probably add the |
||
''; | ||
}; | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
# Copyright (c) 2019-2022, see AUTHORS. Licensed under MIT License, see LICENSE. | ||
|
||
{ pkgs, lib, config, ... }: | ||
zhaofengli marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let | ||
inherit (lib) types; | ||
Gerschtli marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just thinking out loud: Does it make sense to expose Another idea to be more user-friendly and more error-prone (because I am sure there will be a lot of people forgetting to use ''
${cfg.preStart}
exec ${cfg.command}
'' There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do think about splitting set up logic from the actual command? I like the concept of ExecPreStart and similar lifecycle hooks of systemd that we could just implement for the user. If you don't like it, please put a note about the necessity of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should keep it simple and not create illusions of lifecycle hooks that don't actually exist in supervisord. Scripts don't have to include an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But if you set e.g. script = ''
some-command
``; signals like SIGTERM will not be forwarded by default to this process or is supervisord doing some magic there? These signals need to be forwarded to the actual main process. Regarding lifecycle hooks: I agree that that this looks like an illusion but I am just thinking about improving UX through providing useful abstractions |
||
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); | ||
zhaofengli marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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. | ||
zhaofengli marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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; | ||
zhaofengli marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 | ||
zhaofengli marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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; | ||
Gerschtli marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}; | ||
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 <http://supervisord.org/configuration.html#program-x-section-settings>. | ||
''; | ||
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 | ||
''; | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check fails when the supervisord socket file still exists but supervisor isn't running. I'm not sure what a good alternative would be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nc -zU ${config.supervisord.socketPath}
will fail if the socket file isn't actually connected, and succeed without actually sending anything on the socket otherwise.