Initial commit

This commit is contained in:
Sander van der Burg 2020-01-28 00:11:29 +01:00 committed by Sander van der Burg
commit 5fecac210f
100 changed files with 6596 additions and 0 deletions

18
LICENSE Normal file
View File

@ -0,0 +1,18 @@
Copyright (c) 2020 Sander van der Burg
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

458
README.md Normal file
View File

@ -0,0 +1,458 @@
Nix-based process management framework
======================================
This repository contains a very experimental prototype implementation of a
operating system and process manager agnostic Nix-based process managed
framework that can be used to run multiple instances of services on a single
machine, using Nix to deliver and isolate all required package dependencies and
configuration files.
Features:
* It uses *simple conventions* to specify system configurations: function
definitions (corresponding to constructors), function invocations (that
compose running process instances from constructors) and Nix profiles (that
assembles multiple process configurations into one package).
* The ability to deploy *multiple instances* of processes.
* Deploying processes/services as an *unprivileged* user.
* Operating system and process manager *agnostic* -- it can be used on any
operating system that supports the Nix package manager and works with a
variety of process managers.
* Advanced concepts and features, such as namespaces and cgroups, are not
required.
Supported process managers
==========================
Currently, the following process managers are supported:
* `sysvinit`: sysvinit scripts, also known as [LSB Init scripts](https://wiki.debian.org/LSBInitScripts)
* `bsdrc`: BSD [rc scripts](https://www.freebsd.org/doc/en_US.ISO8859-1/articles/rc-scripting/index.html)
* `supervisord`: [supervisord](http://supervisord.org)
* `systemd`: [systemd](https://www.freedesktop.org/wiki/Software/systemd)
* `launchd`: [launchd](https://www.launchd.info)
* `cygrunsrv`: Cygwin's [cygrunsrv](http://web.mit.edu/cygwin/cygwin_v1.3.2/usr/doc/Cygwin/cygrunsrv.README)
Prerequisites
=============
To use this framework, you first need to install:
* [The Nix package manager](http://nixos.org/nix)
* [The Nixpkgs collection](http://nixos.org/nixpkgs)
* [Dysnomia](http://github.com/svanderburg/dysnomia), if you want to manage users and groups
Installation
============
First, make a Git clone of this repository.
The next step is installing the build tool:
```bash
$ cd tools
$ nix-env -f default.nix -iA build
```
Then, at least one tool that deploys a configuration for a supported process
manager must be installed.
For example, to work with sysvinit scripts, you must install:
```bash
$ nix-env -f default.nix -iA sysvinit
```
To work with a different process manager, you should replace `sysvinit` with
any of the supported process managers listed above.
Usage
=====
For each kind of process you need to write a Nix expression that constructs
its configuration from sources and its dependencies. This can be done in a
process manager-specific and a process manager-agnostic way.
Then you need to create a constructors and processes expression.
The processes expression can be used by a deploy tool that works with a
specific process manager.
Writing a process manager-specific process management configuration
-------------------------------------------------------------------
The following expression is an example of a configuration that deploys
a sysvinit script that can be used to control a simple web application
process that just returns a static HTML page:
```nix
{createSystemVInitScript, runtimeDir}:
{port, instanceSuffix ? ""}:
let
webapp = import ../../webapp;
instanceName = "webapp${instanceSuffix}";
in
createSystemVInitScript {
name = instanceName;
inherit instanceName;
process = "${webapp}/bin/webapp";
args = [ "-D" ];
environment = {
PORT = port;
PID_FILE = "${runtimeDir}/${instanceName}.pid";
};
runlevels = [ 3 4 5 ];
user = instanceName;
credentials = {
groups = {
"${instanceName}" = {};
};
users = {
"${instanceName}" = {
group = instanceName;
description = "Webapp";
};
};
};
}
```
A process expression defines a nested function:
* The outer function (first line) allows properties to be configured that apply
to all process instances, such as the the function that constructs sysvinit
scripts (`createSystemVInitScript`) and the `runtimeDir` in which PID files
are stored (on most systems this defaults to: `/var/run`).
* The inner function (second line) refers to all instance specific parameters.
To allow multiple instances to co-exist, instance parameters must be
configured in such a way that they no longer conflict. For example, if we
assign two unique TCP `port` numbers and we append the process name with a
unique suffix, we can run two instances of the web application at the same
time.
In the body, we invoke the `createSystemVInitScript` function to declaratively
construct a sysvinit script:
* The `process` parameter specifies which process should be managed. From this
parameter, the generator will automatically derive `start`, `stop`, `restart`
and `reload` activities.
* The `args` parameter specifies the command line parameters propagated to the
process. The `-D` parameter specifies that the webapp process should run in
daemon mode (i.e. the process spawns another process that keeps running in
the background and then terminates immediately).
* The `environment` attribute set defines all environment variables that the
webapp process needs -- `PORT` is used to specify the TCP port it should
listen to and `PID_FILE` specifies the path to the PID file that stores
the process ID (PID) of the daemon process
* To allow multiple processes to co-exist, we must make sure that each has
a unique PID file name. We can use the `instanceName` parameter to specify
what name this PID file should have. By default, it gets the same name as
the process name.
* The `runlevels` parameter specifies in which run levels the process should
be started (in the above example: 3, 4, and 5. It will automatically
configure the init system to stop the process in the other runlevels:
0, 1, 2, and 6.
* It is also typically not recommended to run a service as root user. The
`credentials` attribute specifies which group and user account need to be
created. The `user` parameter specifies as which user the process needs to
run at.
The `createSystemVInitScript` function support many more parameters than
described in the example above. For example, it is also possible to directly
specify how activities should be implemented.
It can also be used to specify dependencies on other sysvinit scripts -- the
system will automatically derive the sequence numbers so that they are activated
and deactivated in the right order.
In addition to sysvinit, there are also functions that can be used to create
configurations for the other supported process managers, e.g.
`createSystemdUnit`, `createSupervisordProgram`, `createBSDRCScript`. Check
the implementations in `nixproc/create-managed-process` for more information.
Writing a process manager-agnostic process management configuration
-------------------------------------------------------------------
This repository contains generator functions for a variety of process managers.
What you will notice is that they require parameters that looks quite similar.
When it is desired to target multiple process managers, it is also possible to
write a process manager-agnostic configuration from which a variety of
configurations can be generated.
This expression is a process manager-agnostic version of the previous example:
```nix
{createManagedProcess, runtimeDir}:
{port, instanceSuffix ? ""}:
let
webapp = import ../../webapp;
instanceName = "webapp${instanceSuffix}";
in
createManagedProcess {
name = instanceName;
description = "Simple web application";
inherit instanceName;
# This expression can both run in foreground or daemon mode.
# The process manager can pick which mode it prefers.
process = "${webapp}/bin/webapp";
daemonArgs = [ "-D" ];
environment = {
PORT = port;
PID_FILE = "${runtimeDir}/${instanceName}.pid";
};
user = instanceName;
credentials = {
groups = {
"${instanceName}" = {};
};
users = {
"${instanceName}" = {
group = instanceName;
description = "Webapp";
};
};
};
overrides = {
sysvinit = {
runlevels = [ 3 4 5 ];
};
};
}
```
In the above example, we invoke `createManagedProcess` to construct a
configuration for a process manager. It captures similar properties that
are described in the sysvinit-specific configuration, as shown in the previous
example.
To allow it to target a variety of process managers, we must specify:
* How the process can be started in foreground mode. The `process`
parameter gets translated to `foregroundProcess` and `daemon`. The former
specifies how the service should be started as a foreground process and the
latter how it should start as a daemon.
* The `daemonArgs` parameter specifies which command-line parameters the process
should take
Under the hood, the `createManagedProcess` function invokes a generator function
which calls the corresponding process manager-specific create function.
The `createManagedProcess` abstraction function does not support all
functionality that the process manager-specific abstraction functions provide --
It only supports a common subset. To get non-standardized functionality working,
you can also define `overrides`, that augments the generated function parameters
with process manager-specific parameters.
In the above example, we define an override to specify the `runlevels`. Runlevels
is a concept only supported by sysvinit scripts.
Writing a constructors expression
---------------------------------
As shown in the previous sections, a process configuration is a nested function.
To be able to deploy a certain process configuration, it needs to be composed
twice.
The common parameters (the outer function) are composed in a so-called
constructors expression, that has the following structure:
```nix
{ pkgs
, stateDir
, logDir
, runtimeDir
, tmpDir
, forceDisableUserChange
, processManager
}:
let
createManagedProcess = import ../../nixproc/create-managed-process/agnostic/create-managed-process-universal.nix {
inherit pkgs runtimeDir tmpDir forceDisableUserChange processManager;
};
in
{
webapp = import ./webapp.nix {
inherit createManagedProcess runtimeDir;
};
nginxReverseProxy = import ./nginx-reverse-proxy.nix {
inherit createManagedProcess stateDir logDir runtimeDir forceDisableUserChange;
inherit (pkgs) stdenv writeTextFile nginx;
};
}
```
The above Nix expression defines a function that takes common state
configuration parameters that applies to all services:
* `pkgs` refers to the Nixpkgs collection
* `stateDir` refers to the base directory where all variable data needs to be
stored. The default on most systems is `/var`.
* `runtimeDir` refers to the base directory where all PID files are stored
* `tmpDir` refers to the base directory where all temp files are stored
* `forceDisableUserChange` can be used to globally switch of the creation of
users and groups and changing users.
* `processManager` specifies which process manager we want to use (when it is
desired to do process manager agnostic deployments).
In the body, the function returns an attribute set in which every value refers
to a constructor function that can be used to construct process instances.
The `webapp` attribute refers to a constructor function that can be used to
construct one or more running `webapp` processes. It takes the common parameters
that it requires as function arguments.
The constructors attribute facilitates multiple constructor functions.
`nginxReverseProxy` refers to a process configuration that launches the
[Nginx](http://nginx.com) HTTP server and configures it to act as a reverse
proxy for an arbitrary number of web application processes.
Writing a processes expression
------------------------------
The processes Nix expression makes it possible to construct one or more
instances of processes:
```nix
{ pkgs ? import <nixpkgs> { inherit system; }
, system ? builtins.currentSystem
, stateDir ? "/var"
, runtimeDir ? "${stateDir}/run"
, logDir ? "${stateDir}/log"
, tmpDir ? (if stateDir == "/var" then "/tmp" else "${stateDir}/tmp")
, forceDisableUserChange ? false
, processManager
}:
let
constructors = import ./constructors.nix {
inherit pkgs stateDir runtimeDir logDir tmpDir forceDisableUserChange processManager;
};
in
rec {
webapp1 = rec {
port = 5000;
dnsName = "webapp1.local";
pkg = constructors.webapp {
inherit port;
};
};
webapp2 = rec {
port = 5001;
dnsName = "webapp2.local";
pkg = constructors.webapp {
inherit port;
};
};
nginxReverseProxy = rec {
port = 8080;
pkg = constructors.nginxReverseProxy {
webapps = [ webapp1 webapp2 ];
inherit port;
} {};
};
}
```
The above Nix expression is a function in which the parameters (again) specify
common state configuration parameters. It makes a reference the constructors
expression shown in the previous example.
The function body returns an attribute set that defines three process
instances:
* `webapp1` is a web application process that listens on TCP port 5000
* `webapp2` is a web application process that listens on TCP port 5001
* `nginxReverseProxy` is an Nginx server that forwards requests to the
web application processes. If the virtual host is `webapp1.local` then the
first `webapp1` process responds, if the virtual host is `webapp2.local` then
the second process (`webapp2`) responds.
Building a process configurations profile
-----------------------------------------
We can build all required packages and generate all configuration artifacts for
a specific process manager by running the following command:
```bash
$ nixproc-build --process-manager sysvinit processes.nix
result/
```
The above command generates sysvinit scripts and start and stop symlinks to
ensure that the webapp processes are started before the Nginx reverse proxy.
The `--process-manager` parameter can be changed to generate configuration files
for different process managers. For example, if we would use
`--process-manager systemd` then the resulting Nix profile contains a collection
of systemd unit configuration files.
Deploying a process configurations profile
------------------------------------------
In addition to generating configuration files that can be consumed by a process
manager, we can also invoke the process manager to deploy all process defined in
our process Nix expression.
The following command automatically starts all sysvinit scripts (and stop all
obsolete sysvinit scripts, in case of an upgrade):
```bash
$ nixproc-sysvinit-switch processes.nix
```
Consult the help pages of the corresponding process manager specific tools to
get a better understanding on how they work.
Changing the state directories
------------------------------
By default, the all processes use the `/var` directory as a base directory to
store all state. This location can be adjusted by using the `--state-dir`
parameter.
The following command deploys all process instances and stores their state in
`/home/sander/var`:
```bash
$ nixproc-sysvinit-switch --state-dir /home/sander/var processes.nix
```
Similarly, it is also possible to adjust the base locations of the runtime
files, log files and temp files, if desired.
Deploying process instances as an unprivileged user
---------------------------------------------------
It is also possible to do unprivileged user deployments. Unfortunately,
unprivileged users cannot create new groups and/or users or change permissions
of running processes.
This can be globally disabled with the `--force-disable-user-change` parameter.
The following command makes it possible to deploy all processes as an
unprivileged user:
```bash
$ nixproc-sysvinit-switch --state-dir /home/sander/var --force-disable-user-change processes.nix
```
Examples
========
This repository contains three example systems, that can be found in the
`examples/` folder:
* `webapps-sysvinit` is a processes configuration set using the example `webapp`
process described in this README, with `nginx` reverse proxies. The
configuration is specifically written to use sysvinit as a process manager.
* `webapps-agnostic` is the same as the previous example, but using a process
manager agnostic configuration. It can be used to target all process managers
that this toolset supports.
* `services-agnostic` is a process manager-agnostic configuration set of common
system services: Apache HTTP server, MySQL, PostgreSQL and Apache Tomcat.
License
=======
The contents of this package is available under the same license as Nixpkgs --
the [MIT](https://opensource.org/licenses/MIT) license.

View File

@ -0,0 +1,34 @@
{createManagedProcess, apacheHttpd}:
{instanceSuffix ? "", configFile, initialize ? ""}:
let
instanceName = "httpd${instanceSuffix}";
user = instanceName;
group = instanceName;
in
createManagedProcess {
name = instanceName;
inherit instanceName initialize;
process = "${apacheHttpd}/bin/httpd";
args = [ "-f" configFile ];
foregroundProcessExtraArgs = [ "-DFOREGROUND" ];
credentials = {
groups = {
"${group}" = {};
};
users = {
"${user}" = {
inherit group;
description = "Apache HTTP daemon user";
};
};
};
overrides = {
sysvinit = {
runlevels = [ 3 4 5 ];
};
};
}

View File

@ -0,0 +1,49 @@
{ pkgs
, stateDir
, logDir
, runtimeDir
, tmpDir
, forceDisableUserChange
, processManager
}:
let
createManagedProcess = import ../../nixproc/create-managed-process/agnostic/create-managed-process-universal.nix {
inherit pkgs runtimeDir tmpDir forceDisableUserChange processManager;
};
in
{
apache = import ./apache.nix {
inherit createManagedProcess;
inherit (pkgs) apacheHttpd;
};
static-webapp-apache = import ./static-webapp-apache.nix {
inherit createManagedProcess logDir runtimeDir forceDisableUserChange;
inherit (pkgs) stdenv apacheHttpd writeTextFile;
};
mysql = import ./mysql.nix {
inherit createManagedProcess stateDir runtimeDir forceDisableUserChange;
inherit (pkgs) stdenv mysql;
};
postgresql = import ./postgresql.nix {
inherit createManagedProcess stateDir runtimeDir forceDisableUserChange;
inherit (pkgs) stdenv postgresql;
};
tomcat = import ./tomcat.nix {
inherit createManagedProcess stateDir runtimeDir tmpDir forceDisableUserChange;
inherit (pkgs) stdenv;
jre = pkgs.jre8;
tomcat = pkgs.tomcat9;
};
simple-appserving-tomcat = import ./simple-appserving-tomcat.nix {
inherit createManagedProcess stateDir runtimeDir tmpDir forceDisableUserChange;
inherit (pkgs) stdenv;
jre = pkgs.jre8;
tomcat = pkgs.tomcat9;
};
}

View File

@ -0,0 +1,49 @@
{createManagedProcess, stdenv, mysql, stateDir, runtimeDir, forceDisableUserChange}:
{port ? 3306, instanceSuffix ? ""}:
let
instanceName = "mysqld${instanceSuffix}";
dataDir = "${stateDir}/db/${instanceName}";
user = instanceName;
group = instanceName;
in
createManagedProcess {
name = instanceName;
inherit instanceName user;
initialize = ''
mkdir -m0700 -p ${dataDir}
mkdir -m0700 -p ${runtimeDir}
${stdenv.lib.optionalString (!forceDisableUserChange) ''
chown ${user}:${group} ${dataDir}
''}
if [ ! -e "${dataDir}/mysql" ]
then
${mysql}/bin/mysql_install_db --basedir=${mysql} --datadir=${dataDir} ${if forceDisableUserChange then "" else "--user=${user}"}
fi
'';
foregroundProcess = "${mysql}/bin/mysqld";
foregroundProcessArgs = [ "--basedir" mysql "--datadir" dataDir "--port" port "--socket" "${runtimeDir}/${instanceName}.sock" ]
++ stdenv.lib.optionals (!forceDisableUserChange) [ "--user" user ];
credentials = {
groups = {
"${group}" = {};
};
users = {
"${user}" = {
inherit group;
description = "MySQL user";
};
};
};
overrides = {
sysvinit = {
runlevels = [ 3 4 5 ];
};
};
}

View File

@ -0,0 +1,57 @@
{createManagedProcess, stdenv, postgresql, stateDir, runtimeDir, forceDisableUserChange}:
{port ? 5432, instanceSuffix ? ""}:
let
instanceName = "postgresql${instanceSuffix}";
dataDir = "${stateDir}/db/${instanceName}/data";
socketDir = "${runtimeDir}/${instanceName}";
user = instanceName;
group = instanceName;
in
createManagedProcess rec {
name = instanceName;
inherit instanceName user;
initialize = ''
mkdir -m0700 -p ${socketDir}
mkdir -m0700 -p ${dataDir}
${stdenv.lib.optionalString (!forceDisableUserChange) ''
chown ${user}:${group} ${socketDir}
chown ${user}:${group} ${dataDir}
''}
if [ ! -e "${dataDir}/PG_VERSION" ]
then
${postgresql}/bin/initdb -D ${dataDir} --no-locale
fi
'';
process = "${postgresql}/bin/postgres";
args = [ "-D" dataDir "-p" port "-k" socketDir ];
credentials = {
groups = {
"${group}" = {};
};
users = {
"${user}" = {
inherit group;
description = "PostgreSQL user";
};
};
};
overrides = {
sysvinit = {
runlevels = [ 3 4 5 ];
instructions.start = {
activity = "Starting";
instruction = ''
${initialize}
${postgresql}/bin/pg_ctl -D ${dataDir} -o "-p ${toString port} -k ${socketDir}" start
'';
};
};
};
}

View File

@ -0,0 +1,48 @@
{ pkgs ? import <nixpkgs> { inherit system; }
, system ? builtins.currentSystem
, stateDir ? "/var"
, runtimeDir ? "${stateDir}/run"
, logDir ? "${stateDir}/log"
, tmpDir ? (if stateDir == "/var" then "/tmp" else "${stateDir}/tmp")
, forceDisableUserChange ? false
, processManager ? "sysvinit"
}:
let
constructors = import ./constructors.nix {
inherit pkgs stateDir runtimeDir logDir tmpDir forceDisableUserChange processManager;
};
in
rec {
static-webapp-apache = rec {
port = 8080;
pkg = constructors.static-webapp-apache {
inherit port;
};
};
mysql = rec {
port = 3307;
pkg = constructors.mysql {
inherit port;
};
};
postgresql = rec {
port = 6432;
pkg = constructors.postgresql {
inherit port;
};
};
simple-appserving-tomcat = rec {
httpPort = 8081;
pkg = constructors.simple-appserving-tomcat {
inherit httpPort;
};
};
}

View File

@ -0,0 +1,29 @@
{createManagedProcess, stdenv, tomcat, jre, stateDir, runtimeDir, tmpDir, forceDisableUserChange}:
{instanceSuffix ? "", serverPort ? 8005, httpPort ? 8080, httpsPort ? 8443, ajpPort ? 8009}:
let
tomcatConfigFiles = stdenv.mkDerivation {
name = "tomcat-config-files";
buildCommand = ''
mkdir -p $out
cd $out
mkdir conf
cp ${tomcat}/conf/* conf
sed -i \
-e 's|<Server port="8005" shutdown="SHUTDOWN">|<Server port="${toString serverPort}" shutdown="SHUTDOWN">|' \
-e 's|<Connector port="8080" protocol="HTTP/1.1"|<Connector port="${toString httpPort}" protocol="HTTP/1.1"|' \
-e 's|redirectPort="8443"|redirectPort="${toString httpsPort}"|' \
-e 's|<Connector port="8009" protocol="AJP/1.3"|<Connector port="${toString ajpPort}" protocol="AJP/1.3"|' \
conf/server.xml
mkdir webapps
cp -av ${tomcat.webapps}/webapps/* webapps
'';
};
in
import ./tomcat.nix {
inherit createManagedProcess stdenv tomcat jre stateDir runtimeDir tmpDir forceDisableUserChange;
} {
inherit tomcatConfigFiles instanceSuffix;
}

View File

@ -0,0 +1,73 @@
{createManagedProcess, stdenv, apacheHttpd, writeTextFile, logDir, runtimeDir, forceDisableUserChange}:
{instanceSuffix ? "", port ? 80}:
let
instanceName = "httpd${instanceSuffix}";
user = instanceName;
group = instanceName;
modules = [
"mpm_prefork"
"authn_file"
"authn_core"
"authz_host"
"authz_groupfile"
"authz_user"
"authz_core"
"access_compat"
"auth_basic"
"reqtimeout"
"filter"
"mime"
"log_config"
"env"
"headers"
"setenvif"
"version"
"unixd"
"status"
"autoindex"
"alias"
"dir"
];
apacheLogDir = "${logDir}/${instanceName}";
in
import ./apache.nix {
inherit createManagedProcess apacheHttpd;
} {
inherit instanceSuffix;
initialize = ''
mkdir -m0700 -p ${apacheLogDir}
${stdenv.lib.optionalString (!forceDisableUserChange) ''
chown ${user}:${group} ${apacheLogDir}
''}
'';
configFile = writeTextFile {
name = "httpd.conf";
text = ''
ErrorLog "${apacheLogDir}/error_log"
PidFile "${runtimeDir}/${instanceName}.pid"
${stdenv.lib.optionalString (!forceDisableUserChange) ''
User ${user}
Group ${group}
''}
ServerName localhost
ServerRoot ${apacheHttpd}
Listen ${toString port}
${stdenv.lib.concatMapStrings (module: ''
LoadModule ${module}_module ${apacheHttpd}/modules/mod_${module}.so
'') modules}
ServerAdmin root@localhost
DocumentRoot "${./webapp}"
'';
};
}

View File

@ -0,0 +1,61 @@
{createManagedProcess, stdenv, tomcat, jre, stateDir, runtimeDir, tmpDir, forceDisableUserChange}:
{instanceSuffix ? "", tomcatConfigFiles}:
let
instanceName = "tomcat${instanceSuffix}";
baseDir = "${stateDir}/${instanceName}";
user = instanceName;
group = instanceName;
pidFile = "${runtimeDir}/${instanceName}.pid";
in
createManagedProcess rec {
name = "tomcat";
inherit instanceName user pidFile;
process = "${tomcat}/bin/catalina.sh";
args = [ "run" ];
environment = {
JRE_HOME = jre;
CATALINA_TMPDIR = tmpDir;
CATALINA_BASE = baseDir;
CATALINA_PID = pidFile;
};
initialize = ''
if [ ! -d "${baseDir}" ]
then
mkdir -p ${baseDir}/logs
cd ${baseDir}
cp -av ${tomcatConfigFiles}/* .
chmod -R u+w .
${stdenv.lib.optionalString (!forceDisableUserChange) ''
chown -R ${user}:${group} ${baseDir}
''}
fi
'';
credentials = {
groups = {
"${group}" = {};
};
users = {
"${user}" = {
inherit group;
description = "Tomcat user";
};
};
};
overrides = {
sysvinit = {
instructions.start = {
activity = "Starting";
instruction = ''
${initialize}
${tomcat}/bin/startup.sh
'';
};
runlevels = [ 3 4 5 ];
};
};
}

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<title>Hello world!</title>
</head>
<body>
<h1>Hello world!</h1>
</body>
</html>

View File

@ -0,0 +1,29 @@
{ pkgs
, stateDir
, logDir
, runtimeDir
, tmpDir
, forceDisableUserChange
, processManager
, webappMode # set to 'foreground' to make them all foreground process, 'daemon' to make them all daemons. null is to pick best option for the selected processManager
}:
let
createManagedProcess = import ../../nixproc/create-managed-process/agnostic/create-managed-process-universal.nix {
inherit pkgs runtimeDir tmpDir forceDisableUserChange processManager;
};
webappExpr = if webappMode == "foreground" then ./webapp-fg.nix
else if webappMode == "daemon" then ./webapp-daemon.nix
else ./webapp.nix;
in
{
webapp = import webappExpr {
inherit createManagedProcess runtimeDir;
};
nginxReverseProxy = import ./nginx-reverse-proxy.nix {
inherit createManagedProcess stateDir logDir runtimeDir forceDisableUserChange;
inherit (pkgs) stdenv writeTextFile nginx;
};
}

View File

@ -0,0 +1,5 @@
{infrastructure}:
{
}

View File

@ -0,0 +1,6 @@
{infrastructure}:
{
webapp = [ infrastructure.test1 ];
nginxReverseProxy = [ infrastructure.test2 ];
}

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>Disnix VirtualHosts example</title>
</head>
<body>
This web application is unavailable at the moment!
</body>
</html>

View File

@ -0,0 +1,4 @@
{
test1.properties.hostname = "test1";
test2.properties.hostname = "test2";
}

View File

@ -0,0 +1,41 @@
let
nixproc-generate-config = (import ../../tools {}).generate-config;
in
{
test1 = {pkgs, ...}:
{
dysnomia = {
extraContainerProperties = {
managed-process = {
processManager = "systemd";
NIX_PATH = "/root/.nix-defexpr/channels:nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos:nixos-config=/etc/nixos/configuration.nix:/nix/var/nix/profiles/per-user/root/channels";
};
};
};
services.disnix.enable = true;
services.openssh.enable = true;
networking.firewall.enable = false;
environment.systemPackages = [ pkgs.pythonPackages.supervisor nixproc-generate-config ];
};
test2 = {pkgs, ...}:
{
dysnomia = {
extraContainerProperties = {
managed-process = {
processManager = "sysvinit";
NIX_PATH = "/root/.nix-defexpr/channels:nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos:nixos-config=/etc/nixos/configuration.nix:/nix/var/nix/profiles/per-user/root/channels";
};
};
};
services.disnix.enable = true;
services.openssh.enable = true;
networking.firewall.enable = false;
environment.systemPackages = [ pkgs.pythonPackages.supervisor nixproc-generate-config ];
};
}

View File

@ -0,0 +1,14 @@
{
test1 = {pkgs, ...}:
{
deployment.targetEnv = "virtualbox";
deployment.virtualbox.memorySize = 4096; # megabytes
};
test2 = {pkgs, ...}:
{
deployment.targetEnv = "virtualbox";
deployment.virtualbox.memorySize = 4096; # megabytes
};
}

View File

@ -0,0 +1,84 @@
{createManagedProcess, stdenv, writeTextFile, nginx, runtimeDir, stateDir, logDir, forceDisableUserChange}:
{port ? 80, webapps ? [], instanceSuffix ? ""}:
interDeps:
let
instanceName = "nginx${instanceSuffix}";
user = instanceName;
group = instanceName;
nginxStateDir = "${stateDir}/${instanceName}";
dependencies = webapps ++ (builtins.attrValues interDeps);
generateNginxConf = daemon:
writeTextFile {
name = "nginx.conf";
text = ''
error_log ${nginxStateDir}/logs/error.log;
${stdenv.lib.optionalString (!forceDisableUserChange) ''
user ${user} ${group};
''}
${if daemon then ''
pid ${runtimeDir}/${instanceName}.pid;
'' else ''
daemon off;
''}
events {
worker_connections 190000;
}
http {
${stdenv.lib.concatMapStrings (dependency: ''
upstream webapp${toString dependency.port} {
server localhost:${toString dependency.port};
}
'') webapps}
${stdenv.lib.concatMapStrings (dependencyName:
let
dependency = builtins.getAttr dependencyName interDeps;
in
''
upstream webapp${toString dependency.port} {
server ${dependency.target.properties.hostname}:${toString dependency.port};
}
'') (builtins.attrNames interDeps)}
# Fallback virtual host displaying an error page. This is what users see
# if they connect to a non-deployed web application.
# Without it, nginx redirects to the first available virtual host, giving
# unpredictable results. This could happen while an upgrade is in progress.
server {
listen ${toString port};
server_name aaaa;
root ${./errorpage};
}
${stdenv.lib.concatMapStrings (dependency: ''
server {
listen ${toString port};
server_name ${dependency.dnsName};
location / {
proxy_pass http://webapp${toString dependency.port};
}
}
'') dependencies}
}
'';
};
in
import ./nginx.nix {
inherit createManagedProcess stdenv nginx stateDir forceDisableUserChange;
} {
inherit instanceSuffix;
dependencies = map (webapp: webapp.pkg) dependencies;
daemonConfigFile = generateNginxConf true;
foregroundConfigFile = generateNginxConf false;
}

View File

@ -0,0 +1,43 @@
{createManagedProcess, stdenv, nginx, stateDir, forceDisableUserChange}:
{daemonConfigFile, foregroundConfigFile, dependencies ? [], instanceSuffix ? ""}:
let
instanceName = "nginx${instanceSuffix}";
user = instanceName;
group = instanceName;
nginxLogDir = "${stateDir}/${instanceName}/logs";
in
createManagedProcess {
name = instanceName;
description = "Nginx";
initialize = ''
mkdir -p ${nginxLogDir}
${stdenv.lib.optionalString (!forceDisableUserChange) ''
chown ${user}:${group} ${nginxLogDir}
''}
'';
process = "${nginx}/bin/nginx";
args = [ "-p" "${stateDir}/${instanceName}" ];
foregroundProcessExtraArgs = [ "-c" foregroundConfigFile ];
daemonExtraArgs = [ "-c" daemonConfigFile ];
inherit dependencies instanceName;
credentials = {
groups = {
"${group}" = {};
};
users = {
"${user}" = {
inherit group;
description = "Nginx user";
};
};
};
overrides = {
sysvinit = {
runlevels = [ 3 4 5 ];
};
};
}

View File

@ -0,0 +1,96 @@
{ pkgs ? import <nixpkgs> { inherit system; }
, system ? builtins.currentSystem
, stateDir ? "/var"
, runtimeDir ? "${stateDir}/run"
, logDir ? "${stateDir}/log"
, tmpDir ? (if stateDir == "/var" then "/tmp" else "${stateDir}/tmp")
, forceDisableUserChange ? false
, processManager ? "sysvinit"
, webappMode ? null
}:
let
constructors = import ./constructors.nix {
inherit pkgs stateDir runtimeDir logDir tmpDir forceDisableUserChange processManager webappMode;
};
in
rec {
webapp1 = rec {
port = 5000;
dnsName = "webapp1.local";
pkg = constructors.webapp {
inherit port;
instanceSuffix = "1";
};
};
webapp2 = rec {
port = 5001;
dnsName = "webapp2.local";
pkg = constructors.webapp {
inherit port;
instanceSuffix = "2";
};
};
webapp3 = rec {
port = 5002;
dnsName = "webapp3.local";
pkg = constructors.webapp {
inherit port;
instanceSuffix = "3";
};
};
webapp4 = rec {
port = 5003;
dnsName = "webapp4.local";
pkg = constructors.webapp {
inherit port;
instanceSuffix = "4";
};
};
nginxReverseProxy = rec {
port = 8080;
pkg = constructors.nginxReverseProxy {
webapps = [ webapp1 webapp2 webapp3 webapp4 ];
inherit port;
} {};
};
webapp5 = rec {
port = 6002;
dnsName = "webapp5.local";
pkg = constructors.webapp {
inherit port;
instanceSuffix = "5";
};
};
webapp6 = rec {
port = 6003;
dnsName = "webapp6.local";
pkg = constructors.webapp {
inherit port;
instanceSuffix = "6";
};
};
nginxReverseProxy2 = rec {
port = 8081;
pkg = constructors.nginxReverseProxy {
webapps = [ webapp5 webapp6 ];
inherit port;
instanceSuffix = "2";
} {};
};
}

View File

@ -0,0 +1,35 @@
{ pkgs ? import <nixpkgs> { inherit system; }
, system ? builtins.currentSystem
, stateDir ? "/var"
, runtimeDir ? "${stateDir}/run"
, logDir ? "${stateDir}/log"
, tmpDir ? (if stateDir == "/var" then "/tmp" else "${stateDir}/tmp")
, forceDisableUserChange ? false
, processManager
, webappMode ? null
}:
let
constructors = import ./constructors.nix {
inherit pkgs stateDir runtimeDir logDir tmpDir forceDisableUserChange processManager webappMode;
};
in
rec {
webapp = rec {
port = 5000;
dnsName = "webapp.local";
pkg = constructors.webapp {
inherit port;
};
};
nginxReverseProxy = rec {
port = 8080;
pkg = constructors.nginxReverseProxy {
webapps = [ webapp ];
inherit port;
} {};
};
}

View File

@ -0,0 +1,45 @@
{ pkgs, distribution, invDistribution, system
, stateDir ? "/var"
, runtimeDir ? "${stateDir}/run"
, logDir ? "${stateDir}/log"
, tmpDir ? (if stateDir == "/var" then "/tmp" else "${stateDir}/tmp")
, forceDisableUserChange ? false
, processManager ? null # "sysvinit"
}:
let
constructors = import ./constructors.nix {
inherit pkgs stateDir runtimeDir logDir tmpDir forceDisableUserChange processManager;
webappMode = null;
};
processType =
if processManager == null then "managed-process"
else if processManager == "sysvinit" then "sysvinit-script"
else if processManager == "systemd" then "systemd-unit"
else if processManager == "supervisord" then "supervisord-program"
else throw "Unknown process manager: ${processManager}";
in
rec {
webapp = rec {
name = "webapp";
port = 5000;
dnsName = "webapp.local";
pkg = constructors.webapp {
inherit port;
};
type = processType;
};
nginxReverseProxy = rec {
name = "nginxReverseProxy";
port = 8080;
pkg = constructors.nginxReverseProxy {
inherit port;
};
dependsOn = {
inherit webapp;
};
type = processType;
};
}

View File

@ -0,0 +1,39 @@
{createManagedProcess, runtimeDir}:
{port, instanceSuffix ? ""}:
let
webapp = import ../webapp;
instanceName = "webapp${instanceSuffix}";
in
createManagedProcess {
name = instanceName;
description = "Simple web application";
inherit instanceName;
# This expression only specifies how to run webapp in daemon mode
daemon = "${webapp}/bin/webapp";
daemonArgs = [ "-D" ];
environment = {
PORT = port;
PID_FILE = "${runtimeDir}/${instanceName}.pid";
};
user = instanceName;
credentials = {
groups = {
"${instanceName}" = {};
};
users = {
"${instanceName}" = {
group = instanceName;
description = "Webapp";
};
};
};
overrides = {
sysvinit = {
runlevels = [ 3 4 5 ];
};
};
}

View File

@ -0,0 +1,37 @@
{createManagedProcess, runtimeDir}:
{port, instanceSuffix ? ""}:
let
webapp = import ../webapp;
instanceName = "webapp${instanceSuffix}";
in
createManagedProcess {
name = instanceName;
description = "Simple web application";
inherit instanceName;
# This expression only specifies how to run the webapp in foreground mode
foregroundProcess = "${webapp}/bin/webapp";
environment = {
PORT = port;
};
user = instanceName;
credentials = {
groups = {
"${instanceName}" = {};
};
users = {
"${instanceName}" = {
group = instanceName;
description = "Webapp";
};
};
};
overrides = {
sysvinit = {
runlevels = [ 3 4 5 ];
};
};
}

View File

@ -0,0 +1,40 @@
{createManagedProcess, runtimeDir}:
{port, instanceSuffix ? ""}:
let
webapp = import ../../webapp;
instanceName = "webapp${instanceSuffix}";
in
createManagedProcess {
name = instanceName;
description = "Simple web application";
inherit instanceName;
# This expression can both run in foreground or daemon mode.
# The process manager can pick which mode it prefers.
process = "${webapp}/bin/webapp";
daemonArgs = [ "-D" ];
environment = {
PORT = port;
PID_FILE = "${runtimeDir}/${instanceName}.pid";
};
user = instanceName;
credentials = {
groups = {
"${instanceName}" = {};
};
users = {
"${instanceName}" = {
group = instanceName;
description = "Webapp";
};
};
};
overrides = {
sysvinit = {
runlevels = [ 3 4 5 ];
};
};
}

View File

@ -0,0 +1,34 @@
{ pkgs
, stateDir
, logDir
, runtimeDir
, tmpDir
, forceDisableUserChange
}:
let
createSystemVInitScript = import ../../nixproc/create-managed-process/sysvinit/create-sysvinit-script.nix {
inherit (pkgs) stdenv writeTextFile daemon;
inherit runtimeDir tmpDir forceDisableUserChange;
createCredentials = import ../../nixproc/create-credentials {
inherit (pkgs) stdenv;
};
initFunctions = import ../../nixproc/create-managed-process/sysvinit/init-functions.nix {
basePackages = [ pkgs.coreutils pkgs.gnused pkgs.inetutils pkgs.gnugrep pkgs.sysvinit ];
inherit (pkgs) stdenv fetchurl;
inherit runtimeDir;
};
};
in
{
webapp = import ./webapp.nix {
inherit createSystemVInitScript runtimeDir;
};
nginxReverseProxy = import ./nginx-reverse-proxy.nix {
inherit createSystemVInitScript stateDir logDir runtimeDir;
inherit (pkgs) stdenv writeTextFile nginx;
};
}

View File

@ -0,0 +1,5 @@
{infrastructure}:
{
}

View File

@ -0,0 +1,6 @@
{infrastructure}:
{
webapp = [ infrastructure.test1 ];
nginxReverseProxy = [ infrastructure.test2 ];
}

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>Disnix VirtualHosts example</title>
</head>
<body>
This web application is unavailable at the moment!
</body>
</html>

View File

@ -0,0 +1,17 @@
{
test1 = {pkgs, ...}:
{
services.disnix.enable = true;
services.openssh.enable = true;
networking.firewall.enable = false;
};
test2 = {pkgs, ...}:
{
services.disnix.enable = true;
services.openssh.enable = true;
networking.firewall.enable = false;
};
}

View File

@ -0,0 +1,14 @@
{
test1 = {pkgs, ...}:
{
deployment.targetEnv = "virtualbox";
deployment.virtualbox.memorySize = 4096; # megabytes
};
test2 = {pkgs, ...}:
{
deployment.targetEnv = "virtualbox";
deployment.virtualbox.memorySize = 4096; # megabytes
};
}

View File

@ -0,0 +1,67 @@
{createSystemVInitScript, stdenv, writeTextFile, nginx, runtimeDir, stateDir, logDir}:
{port ? 80, webapps ? [], instanceSuffix ? ""}:
interDeps:
let
instanceName = "nginx${instanceSuffix}";
nginxStateDir = "${stateDir}/${instanceName}";
dependencies = webapps ++ (builtins.attrValues interDeps);
in
import ./nginx.nix {
inherit createSystemVInitScript nginx instanceSuffix;
stateDir = nginxStateDir;
dependencies = map (webapp: webapp.pkg) dependencies;
configFile = writeTextFile {
name = "nginx.conf";
text = ''
error_log ${nginxStateDir}/logs/error.log;
pid ${runtimeDir}/${instanceName}.pid;
events {
worker_connections 190000;
}
http {
${stdenv.lib.concatMapStrings (dependency: ''
upstream webapp${toString dependency.port} {
server localhost:${toString dependency.port};
}
'') webapps}
${stdenv.lib.concatMapStrings (dependencyName:
let
dependency = builtins.getAttr dependencyName interDeps;
in
''
upstream webapp${toString dependency.port} {
server ${dependency.target.properties.hostname}:${toString dependency.port};
}
'') (builtins.attrNames interDeps)}
# Fallback virtual host displaying an error page. This is what users see
# if they connect to a non-deployed web application.
# Without it, nginx redirects to the first available virtual host, giving
# unpredictable results. This could happen while an upgrade is in progress.
server {
listen ${toString port};
server_name aaaa;
root ${./errorpage};
}
${stdenv.lib.concatMapStrings (dependency: ''
server {
listen ${toString port};
server_name ${dependency.dnsName};
location / {
proxy_pass http://webapp${toString dependency.port};
}
}
'') dependencies}
}
'';
};
}

View File

@ -0,0 +1,17 @@
{createSystemVInitScript, nginx, configFile, stateDir, dependencies ? [], instanceSuffix ? ""}:
let
instanceName = "nginx${instanceSuffix}";
in
createSystemVInitScript {
name = instanceName;
description = "Nginx";
initialize = ''
mkdir -p ${stateDir}/logs
'';
process = "${nginx}/bin/nginx";
args = [ "-c" configFile "-p" stateDir ];
runlevels = [ 3 4 5 ];
inherit dependencies instanceName;
}

View File

@ -0,0 +1,94 @@
{ pkgs ? import <nixpkgs> { inherit system; }
, system ? builtins.currentSystem
, stateDir ? "/var"
, runtimeDir ? "${stateDir}/run"
, logDir ? "${stateDir}/log"
, tmpDir ? (if stateDir == "/var" then "/tmp" else "${stateDir}/tmp")
, forceDisableUserChange ? false
}:
let
constructors = import ./constructors.nix {
inherit pkgs stateDir runtimeDir logDir tmpDir forceDisableUserChange;
};
in
rec {
webapp1 = rec {
port = 5000;
dnsName = "webapp1.local";
pkg = constructors.webapp {
inherit port;
instanceSuffix = "1";
};
};
webapp2 = rec {
port = 5001;
dnsName = "webapp2.local";
pkg = constructors.webapp {
inherit port;
instanceSuffix = "2";
};
};
webapp3 = rec {
port = 5002;
dnsName = "webapp3.local";
pkg = constructors.webapp {
inherit port;
instanceSuffix = "3";
};
};
webapp4 = rec {
port = 5003;
dnsName = "webapp4.local";
pkg = constructors.webapp {
inherit port;
instanceSuffix = "4";
};
};
nginxReverseProxy = rec {
port = 8080;
pkg = constructors.nginxReverseProxy {
webapps = [ webapp1 webapp2 webapp3 webapp4 ];
inherit port;
} {};
};
webapp5 = rec {
port = 6002;
dnsName = "webapp5.local";
pkg = constructors.webapp {
inherit port;
instanceSuffix = "5";
};
};
webapp6 = rec {
port = 6003;
dnsName = "webapp6.local";
pkg = constructors.webapp {
inherit port;
instanceSuffix = "6";
};
};
nginxReverseProxy2 = rec {
port = 8081;
pkg = constructors.nginxReverseProxy {
webapps = [ webapp5 webapp6 ];
inherit port;
instanceSuffix = "2";
} {};
};
}

View File

@ -0,0 +1,33 @@
{ pkgs ? import <nixpkgs> { inherit system; }
, system ? builtins.currentSystem
, stateDir ? "/var"
, runtimeDir ? "${stateDir}/run"
, logDir ? "${stateDir}/log"
, tmpDir ? (if stateDir == "/var" then "/tmp" else "${stateDir}/tmp")
, forceDisableUserChange ? false
}:
let
constructors = import ./constructors.nix {
inherit pkgs stateDir runtimeDir logDir tmpDir forceDisableUserChange;
};
in
rec {
webapp = rec {
port = 5000;
dnsName = "webapp.local";
pkg = constructors.webapp {
inherit port;
};
};
nginxReverseProxy = rec {
port = 8080;
pkg = constructors.nginxReverseProxy {
webapps = [ webapp ];
inherit port;
} {};
};
}

View File

@ -0,0 +1,36 @@
{ pkgs, distribution, invDistribution, system
, stateDir ? "/var"
, runtimeDir ? "${stateDir}/run"
, logDir ? "${stateDir}/log"
, tmpDir ? (if stateDir == "/var" then "/tmp" else "${stateDir}/tmp")
, forceDisableUserChange ? true
}:
let
constructors = import ./constructors.nix {
inherit pkgs stateDir runtimeDir logDir tmpDir forceDisableUserChange;
};
in
rec {
webapp = rec {
name = "webapp";
port = 5000;
dnsName = "webapp.local";
pkg = constructors.webapp {
inherit port;
};
type = "sysvinit-script";
};
nginxReverseProxy = rec {
name = "nginxReverseProxy";
port = 8080;
pkg = constructors.nginxReverseProxy {
inherit port;
};
dependsOn = {
inherit webapp;
};
type = "sysvinit-script";
};
}

View File

@ -0,0 +1,33 @@
{createSystemVInitScript, runtimeDir}:
{port, instanceSuffix ? ""}:
let
webapp = import ../../webapp;
instanceName = "webapp${instanceSuffix}";
in
createSystemVInitScript {
name = instanceName;
inherit instanceName;
process = "${webapp}/bin/webapp";
args = [ "-D" ];
environment = {
PORT = port;
PID_FILE = "${runtimeDir}/${instanceName}.pid";
};
runlevels = [ 3 4 5 ];
user = instanceName;
credentials = {
groups = {
"${instanceName}" = {};
};
users = {
"${instanceName}" = {
group = instanceName;
description = "Webapp";
};
};
};
}

View File

@ -0,0 +1,43 @@
{stdenv}:
{groups, users}:
stdenv.mkDerivation {
name = "credentials";
buildCommand = ''
mkdir -p $out/dysnomia-support/groups
${stdenv.lib.concatMapStrings (groupname:
let
group = builtins.getAttr groupname groups;
in
''
cat > $out/dysnomia-support/groups/${groupname} <<EOF
${stdenv.lib.concatMapStrings (propertyName:
let
value = builtins.getAttr propertyName group;
in
"${propertyName}=${stdenv.lib.escapeShellArg value}\n"
) (builtins.attrNames group)}
EOF
''
) (builtins.attrNames groups)}
mkdir -p $out/dysnomia-support/users
${stdenv.lib.concatMapStrings (username:
let
user = builtins.getAttr username users;
in
''
cat > $out/dysnomia-support/users/${username} <<EOF
${stdenv.lib.concatMapStrings (propertyName:
let
value = builtins.getAttr propertyName user;
in
"${propertyName}=${stdenv.lib.escapeShellArg value}\n"
) (builtins.attrNames user)}
EOF
''
) (builtins.attrNames users)}
'';
}

View File

@ -0,0 +1,40 @@
{buildEnv}:
{processes, processManager}:
let
buildSysVInitEnv = import ../sysvinit/build-sysvinit-env.nix {
inherit buildEnv;
};
buildSystemdEnv = import ../systemd/build-systemd-env.nix {
inherit buildEnv;
};
buildSupervisordEnv = import ../supervisord/build-supervisord-env.nix {
inherit buildEnv;
};
buildBSDRCEnv = import ../bsdrc/build-bsdrc-env.nix {
inherit buildEnv;
};
buildLaunchdEnv = import ../launchd/build-launchd-env.nix {
inherit buildEnv;
};
buildCygrunsrvEnv = import ../cygrunsrv/build-cygrunsrv-env.nix {
inherit buildEnv;
};
buildProcessEnvFun =
if processManager == "sysvinit" then buildSysVInitEnv
else if processManager == "systemd" then buildSystemdEnv
else if processManager == "supervisord" then buildSupervisordEnv
else if processManager == "bsdrc" then buildBSDRCEnv
else if processManager == "launchd" then buildLaunchdEnv
else if processManager == "cygrunsrv" then buildCygrunsrvEnv
else throw "Unknown process manager: ${processManager}";
in
buildProcessEnvFun {
inherit processes;
}

View File

@ -0,0 +1,14 @@
{stdenv}:
{name, ...}@properties:
let
configJSON = builtins.toJSON properties;
in
stdenv.mkDerivation {
inherit name configJSON;
passAsFile = [ "configJSON" ];
buildCommand = ''
mkdir -p $out
cp $configJSONPath $out/${name}.json
'';
}

View File

@ -0,0 +1,26 @@
{ configFile
, processManager
, system ? builtins.currentSystem
, pkgs ? import <nixpkgs> { inherit system; }
, stateDir ? "/var"
, runtimeDir ? "${stateDir}/run"
, tmpDir ? (if stateDir == "/var" then "/tmp" else "${stateDir}/tmp")
, forceDisableUserChange ? false
}:
let
createManagedProcessFromConfig = configFile:
let
createManagedProcess = import ./create-managed-process-universal.nix {
inherit pkgs runtimeDir tmpDir forceDisableUserChange processManager;
};
properties = builtins.fromJSON (builtins.readFile configFile);
normalizedProperties = properties // pkgs.stdenv.lib.optionalAttrs (properties ? dependencies) {
dependencies = map (dependency: createManagedProcessFromConfig "${dependency}/${builtins.substring 33 (builtins.stringLength dependency) (baseNameOf dependency)}.json") properties.dependencies;
};
in
createManagedProcess normalizedProperties;
in
createManagedProcessFromConfig configFile

View File

@ -0,0 +1,98 @@
{pkgs, runtimeDir, tmpDir, forceDisableUserChange ? false, processManager ? null}:
let
basePackages = [
pkgs.coreutils
pkgs.gnused
pkgs.gnugrep
pkgs.inetutils
];
createCredentials = import ../../create-credentials {
inherit (pkgs) stdenv;
};
createSystemVInitScript = import ../sysvinit/create-sysvinit-script.nix {
inherit (pkgs) stdenv writeTextFile daemon;
inherit createCredentials runtimeDir tmpDir forceDisableUserChange;
initFunctions = import ../sysvinit/init-functions.nix {
inherit (pkgs) stdenv fetchurl;
inherit runtimeDir;
basePackages = basePackages ++ [ pkgs.sysvinit ];
};
};
generateSystemVInitScript = import ./generate-sysvinit-script.nix {
inherit createSystemVInitScript;
inherit (pkgs) stdenv;
};
createSystemdService = import ../systemd/create-systemd-service.nix {
inherit (pkgs) writeTextFile stdenv;
inherit createCredentials basePackages forceDisableUserChange;
};
generateSystemdService = import ./generate-systemd-service.nix {
inherit createSystemdService;
inherit (pkgs) stdenv writeTextFile;
};
createSupervisordProgram = import ../supervisord/create-supervisord-program.nix {
inherit (pkgs) writeTextFile stdenv;
inherit (pkgs.pythonPackages) supervisor;
inherit createCredentials basePackages forceDisableUserChange runtimeDir;
};
generateSupervisordProgram = import ./generate-supervisord-program.nix {
inherit createSupervisordProgram runtimeDir;
inherit (pkgs) stdenv writeTextFile;
};
createBSDRCScript = import ../bsdrc/create-bsdrc-script.nix {
inherit (pkgs) writeTextFile stdenv;
inherit createCredentials forceDisableUserChange runtimeDir;
rcSubr = import ../bsdrc/rcsubr.nix {
inherit (pkgs) stdenv;
inherit forceDisableUserChange;
};
};
generateBSDRCScript = import ../agnostic/generate-bsdrc-script.nix {
inherit createBSDRCScript;
inherit (pkgs) stdenv;
};
createLaunchdDaemon = import ../launchd/create-launchd-daemon.nix {
inherit (pkgs) writeTextFile stdenv;
inherit createCredentials forceDisableUserChange;
};
generateLaunchdDaemon = import ../agnostic/generate-launchd-daemon.nix {
inherit (pkgs) stdenv writeTextFile;
inherit createLaunchdDaemon runtimeDir;
};
createCygrunsrvParams = import ../cygrunsrv/create-cygrunsrv-params.nix {
inherit (pkgs) writeTextFile stdenv;
};
generateCygrunsrvParams = import ../agnostic/generate-cygrunsrv-params.nix {
inherit (pkgs) stdenv writeTextFile;
inherit createCygrunsrvParams runtimeDir;
};
in
import ./create-managed-process.nix {
inherit processManager;
inherit (pkgs) stdenv;
generateProcessFun =
if processManager == "sysvinit" then generateSystemVInitScript
else if processManager == "systemd" then generateSystemdService
else if processManager == "supervisord" then generateSupervisordProgram
else if processManager == "bsdrc" then generateBSDRCScript
else if processManager == "launchd" then generateLaunchdDaemon
else if processManager == "cygrunsrv" then generateCygrunsrvParams
else throw "Unknown process manager: ${processManager}";
}

View File

@ -0,0 +1,59 @@
{ generateProcessFun, processManager, stdenv }:
{
# A name that identifies the process instance
name
# A more human-readable description of the process
, description ? name
# Shell commands that specify how the state should be initialized
, initialize ? ""
# Path to a process to execute (both in foreground and daemon mode)
, process ? null
# Generic command-line parameters propagated to the process
, args ? []
# The executable that needs to run to start the process is daemon mode
, daemon ? process
# Extra arguments appended to args when the process runs in daemon mode
, daemonExtraArgs ? []
# Command-line arguments propagated to the daemon
, daemonArgs ? (args ++ daemonExtraArgs)
# A name that uniquely identifies each process instance. It is used to generate a unique PID file.
, instanceName ? null
# Path to a PID file that the system should use to manage the process. If null, it will use a default path.
, pidFile ? null
# The executable that needs to run to start the process in foreground mode
, foregroundProcess ? process
# Extra arguments appended to args when the process runs in foreground mode
, foregroundProcessExtraArgs ? []
# Command-line arguments propagated to the foreground process
, foregroundProcessArgs ? (args ++ foregroundProcessExtraArgs)
# Specifies which packages need to be in the PATH
, path ? []
# An attribute set specifying arbitrary environment variables
, environment ? {}
# If not null, the current working directory will be changed before executing any activities
, directory ? null
# If not null, the umask will be changed before executing any activities
, umask ? null
# If not null, the nice level be changed before executing any activities
, nice ? null
# Specifies as which user the process should run. If null, the user privileges will not be changed.
, user ? null
# Dependencies on other processes. Typically, this specification is used to derive the activation order.
, dependencies ? []
# Specifies which groups and users that need to be created.
, credentials ? {}
# Specifies process manager specific properties that augmented to the generated function parameters
, overrides ? {}
}@properties:
let
createAgnosticConfig = import ./create-agnostic-config.nix {
inherit stdenv;
};
in
if processManager == null then createAgnosticConfig properties
else generateProcessFun {
inherit name description initialize daemon daemonArgs instanceName pidFile foregroundProcess foregroundProcessArgs path environment directory umask nice user dependencies credentials;
overrides = if builtins.hasAttr processManager overrides then builtins.getAttr processManager overrides else {};
}

View File

@ -0,0 +1,37 @@
{ createBSDRCScript, stdenv }:
{ name
, description
, initialize
, daemon
, daemonArgs
, instanceName
, pidFile
, foregroundProcess
, foregroundProcessArgs
, path
, environment
, directory
, umask
, nice
, user
, dependencies
, credentials
, overrides
}:
# TODO: umask
createBSDRCScript (stdenv.lib.recursiveUpdate ({
inherit name environment path directory nice dependencies;
inherit user instanceName credentials;
command = if daemon != null then daemon else foregroundProcess;
commandIsDaemon = daemon != null;
commandArgs = if daemon != null then daemonArgs else foregroundProcessArgs;
} // stdenv.lib.optionalAttrs (pidFile != null) {
inherit pidFile;
} // stdenv.lib.optionalAttrs (initialize != "") {
commands.start.pre = initialize;
}) overrides)

View File

@ -0,0 +1,57 @@
{ createCygrunsrvParams
, stdenv
, writeTextFile
, runtimeDir ? "/var/run"
}:
{ name
, description
, initialize
, daemon
, daemonArgs
, instanceName
, pidFile
, foregroundProcess
, foregroundProcessArgs
, path
, environment
, directory
, umask
, nice
, user
, dependencies
, credentials
, overrides
}:
# TODO: credentials
# TODO: directory unused
# TODO: umask unused
# TODO: nice unused
# TODO: user unused
let
generateForegroundWrapper = import ./generate-foreground-wrapper.nix {
inherit stdenv writeTextFile;
};
in
createCygrunsrvParams (stdenv.lib.recursiveUpdate ({
inherit name environment dependencies;
environmentPath = path;
path = if foregroundProcess != null then
if initialize == "" then foregroundProcess
else generateForegroundWrapper {
wrapDaemon = false;
executable = foregroundProcess;
inherit name initialize runtimeDir pidFile stdenv;
}
else generateForegroundWrapper {
wrapDaemon = true;
executable = daemon;
inherit name initialize runtimeDir pidFile stdenv;
};
args = if foregroundProcess != null then foregroundProcessArgs else daemonArgs;
}) overrides)

View File

@ -0,0 +1,83 @@
{stdenv, writeTextFile}:
{ name
, wrapDaemon
, initialize
, executable
, stdenv
, runtimeDir
, instanceName ? null
, pidFile ? (if instanceName == null then null else "${runtimeDir}/${instanceName}.pid")
}:
let
_pidFile = if pidFile == null then "${runtimeDir}/$(basename ${executable}).pid" else pidFile;
in
writeTextFile {
name = "${name}-foregroundwrapper.sh";
text = ''
#! ${stdenv.shell} -e
${initialize}
${if wrapDaemon then ''
export _TOP_PID=$$
# Handle to SIGTERM and SIGINT signals and forward them to the daemon process
_term()
{
trap "exit 0" TERM
kill -TERM "$pid"
kill $_TOP_PID
}
_interrupt()
{
kill -INT "$pid"
}
trap _term SIGTERM
trap _interrupt SIGINT
# Start process in the background as a daemon
${executable} "$@"
# Wait for the PID file to become available. Useful to work with daemons that don't behave well enough.
count=0
while [ ! -f "${_pidFile}" ]
do
if [ $count -eq 10 ]
then
echo "It does not seem that there isn't any pid file! Giving up!"
exit 1
fi
echo "Waiting for ${_pidFile} to become available..."
sleep 1
((count=count++))
done
# Determine the daemon's PID by using the PID file
pid=$(cat ${_pidFile})
# Wait in the background for the PID to terminate
${if stdenv.isDarwin then ''
lsof -p $pid +r 3 &>/dev/null &
'' else if stdenv.isLinux || stdenv.isCygwin then ''
tail --pid=$pid -f /dev/null &
'' else if stdenv.isBSD || stdenv.isSunOS then ''
pwait $pid &
'' else throw "Don't know how to wait for process completion on system: ${stdenv.system}"}
# Wait for the blocker process to complete. We use wait, so that bash can still
# handle the SIGTERM and SIGINT signals that may be sent to it by a process
# manager
blocker_pid=$!
wait $blocker_pid
'' else ''
exec "${executable}" "$@"
''}
'';
executable = true;
}

View File

@ -0,0 +1,73 @@
{ createLaunchdDaemon
, stdenv
, writeTextFile
, runtimeDir ? "/var/run"
}:
{ name
, description
, initialize
, daemon
, daemonArgs
, instanceName
, pidFile
, foregroundProcess
, foregroundProcessArgs
, path
, environment
, directory
, umask
, nice
, user
, dependencies
, credentials
, overrides
}:
let
generateForegroundWrapper = import ./generate-foreground-wrapper.nix {
inherit stdenv writeTextFile;
};
Program = if foregroundProcess != null then
if initialize == "" then foregroundProcess
else generateForegroundWrapper ({
wrapDaemon = false;
executable = foregroundProcess;
inherit name initialize runtimeDir stdenv;
} // stdenv.lib.optionalAttrs (instanceName != null) {
inherit instanceName;
} // stdenv.lib.optionalAttrs (pidFile != null) {
inherit pidFile;
})
else generateForegroundWrapper ({
wrapDaemon = true;
executable = daemon;
inherit name initialize runtimeDir stdenv;
} // stdenv.lib.optionalAttrs (instanceName != null) {
inherit instanceName;
} // stdenv.lib.optionalAttrs (pidFile != null) {
inherit pidFile;
});
ProgramArguments = [ Program ] ++ (if foregroundProcess != null then foregroundProcessArgs else daemonArgs);
daemonConfig = createLaunchdDaemon (stdenv.lib.recursiveUpdate ({
inherit name credentials Program;
} // stdenv.lib.optionalAttrs (ProgramArguments != [ Program ]) {
inherit ProgramArguments;
} // stdenv.lib.optionalAttrs (environment != {}) {
EnvironmentVariables = environment;
} // stdenv.lib.optionalAttrs (path != []) {
inherit path;
} // stdenv.lib.optionalAttrs (directory != null) {
WorkingDirectory = directory;
} // stdenv.lib.optionalAttrs (umask != null) {
Umask = umask;
} // stdenv.lib.optionalAttrs (nice != null) {
Nice = nice;
} // stdenv.lib.optionalAttrs (user != null) {
UserName = user;
}) overrides);
in
if dependencies == [] then daemonConfig else
builtins.trace "WARNING: dependencies have been specified for process: ${name}, but launchd has no notion of process dependencies. Proper activation ordering cannot be guaranteed!" daemonConfig

View File

@ -0,0 +1,11 @@
{ stdenv, writeTextFile }:
{ name, initialize }:
writeTextFile {
name = "${name}-prestart";
executable = true;
text = ''
#! ${stdenv.shell} -e
${initialize}
'';
}

View File

@ -0,0 +1,60 @@
{ createSupervisordProgram, stdenv, writeTextFile, runtimeDir }:
{ name
, description
, initialize
, daemon
, daemonArgs
, instanceName
, pidFile
, foregroundProcess
, foregroundProcessArgs
, path
, environment
, directory
, umask
, nice
, user
, dependencies
, credentials
, overrides
}:
let
generateForegroundWrapper = import ./generate-foreground-wrapper.nix {
inherit stdenv writeTextFile;
};
command = if foregroundProcess != null then
(if initialize == ""
then foregroundProcess
else generateForegroundWrapper ({
wrapDaemon = false;
executable = foregroundProcess;
inherit name initialize runtimeDir stdenv;
} // stdenv.lib.optionalAttrs (instanceName != null) {
inherit instanceName;
} // stdenv.lib.optionalAttrs (pidFile != null) {
inherit pidFile;
})) + " ${toString foregroundProcessArgs}"
else (generateForegroundWrapper ({
wrapDaemon = true;
executable = daemon;
inherit name initialize runtimeDir stdenv;
} // stdenv.lib.optionalAttrs (instanceName != null) {
inherit instanceName;
} // stdenv.lib.optionalAttrs (pidFile != null) {
inherit pidFile;
})) + " ${toString daemonArgs}";
in
createSupervisordProgram (stdenv.lib.recursiveUpdate ({
inherit name command path environment dependencies credentials;
} // stdenv.lib.optionalAttrs (umask != null) {
inherit umask;
} // stdenv.lib.optionalAttrs (nice != null) {
inherit nice;
} // stdenv.lib.optionalAttrs (pidFile != null) {
inherit pidFile;
} // stdenv.lib.optionalAttrs (user != null) {
inherit user;
}) overrides)

View File

@ -0,0 +1,52 @@
{ createSystemdService, stdenv, writeTextFile }:
{ name
, description
, initialize
, daemon
, daemonArgs
, instanceName
, pidFile
, foregroundProcess
, foregroundProcessArgs
, path
, environment
, directory
, umask
, nice
, user
, dependencies
, credentials
, overrides
}:
let
generatePreStartScript = import ./generate-prestart-script.nix {
inherit stdenv writeTextFile;
};
in
createSystemdService (stdenv.lib.recursiveUpdate {
inherit name path environment dependencies credentials;
Unit = {
Description = description;
};
Service = {
ExecStart = if foregroundProcess != null then "${foregroundProcess} ${toString foregroundProcessArgs}" else "${daemon} ${toString daemonArgs}";
Type = if foregroundProcess != null then "simple" else "forking";
} // stdenv.lib.optionalAttrs (initialize != "") {
ExecStartPre = stdenv.lib.optionalString (user != null) "+" + generatePreStartScript {
inherit name initialize;
};
} // stdenv.lib.optionalAttrs (directory != null) {
WorkingDirectory = directory;
} // stdenv.lib.optionalAttrs (umask != null) {
UMask = umask;
} // stdenv.lib.optionalAttrs (nice != null) {
Nice = nice;
} // stdenv.lib.optionalAttrs (foregroundProcess == null && pidFile != null) {
PIDFile = pidFile;
} // stdenv.lib.optionalAttrs (user != null) {
User = user;
};
} overrides)

View File

@ -0,0 +1,32 @@
{ createSystemVInitScript, stdenv }:
{ name
, description
, initialize
, daemon
, daemonArgs
, instanceName
, pidFile
, foregroundProcess
, foregroundProcessArgs
, path
, environment
, directory
, umask
, nice
, user
, dependencies
, credentials
, overrides
}:
createSystemVInitScript (stdenv.lib.recursiveUpdate ({
inherit name description path environment directory umask nice dependencies credentials;
inherit instanceName initialize user;
process = if daemon != null then daemon else foregroundProcess;
processIsDaemon = daemon != null;
args = if daemon != null then daemonArgs else foregroundProcessArgs;
} // stdenv.lib.optionalAttrs (pidFile != null) {
inherit pidFile;
}) overrides)

View File

@ -0,0 +1,30 @@
{ pkgs ? import <nixpkgs> { inherit system; }
, system ? builtins.currentSystem
, stateDir ? "/var"
, runtimeDir ? "${stateDir}/run"
, logDir ? "${stateDir}/log"
, tmpDir ? (if stateDir == "/var" then "/tmp" else "${stateDir}/tmp")
, forceDisableUserChange ? false
, exprFile
}@args:
let
processesFun = import exprFile;
processesFormalArgs = builtins.functionArgs processesFun;
processesArgs = builtins.intersectAttrs processesFormalArgs (args // {
processManager = "bsdrc";
});
processes = processesFun processesArgs;
in
pkgs.buildEnv {
name = "rc.d";
paths = map (processName:
let
process = builtins.getAttr processName processes;
in
process.pkg
) (builtins.attrNames processes);
}

View File

@ -0,0 +1,237 @@
{ writeTextFile
, stdenv
, createCredentials
# Path to the rc.subr script
, rcSubr ? "/etc/rc.subr"
# Specifies which command are builtin. This is to determine which extra commands may have been provided.
, builtinCommands ? [ "start" "stop" "reload" "restart" "status" "poll" "rcvar" ]
# Specifies the default signal used for reloading a process
, defaultReloadSignal ? "HUP"
# Specifies the default signal used for stopping a process
, defaultStopSignal ? "TERM"
# Default run time directory where PID files are stored
, runtimeDir ? "/var/run"
# Specifies whether user changing functionality should be disabled or not
, forceDisableUserChange ? false
}:
{
# A name that identifies the process instance
name
# The variable suffix that indicates whether a service has been enabled or not.
, rcvar ? "enabled"
# An attribute set defining default values for configuration environment variables
, rcvarsDefaults ? {}
# The command that executes a daemon
, command ? null
# The command-line parameters passed to the command
, commandArgs ? []
# Specifies whether the command daemonizes or not. If the command is not a daemon, it gets daemonized by the generator
, commandIsDaemon ? true
# Specifies the signal that needs to be sent to reload a process
, reloadSignal ? "HUP"
# Specifies the signal that needs to be sent to stop a process
, stopSignal ? "TERM"
# Specifies which packages need to be in the PATH
, environment ? {}
# An attribute set specifying arbitrary environment variables
, path ? []
# A name that uniquely identifies each process instance. It is used to generate a unique PID file.
, instanceName ? null
# Path to a PID file that the system should use to manage the process. If null, it will use a default path.
, pidFile ? (if instanceName == null then null else "${runtimeDir}/${instanceName}.pid")
# If not null, the nice level be changed before executing any activities
, nice ? null
# If not null, the current working directory will be changed before executing any activities
, directory ? null
# Specifies as which user the process should run. If null, the user privileges will not be changed.
, user ? null
# Defines files that must be readable before running the start method
, requiredFiles ? []
# Defines directories that must exist before running the start method
, requiredDirs ? []
# Defines external environment variables this script depends on
, requiredVars ? []
# Perform checkyesno on each of the list variables before running the start method.
, requiredModules ? []
# Specifies the implementation of arbitrary commands
, commands ? {}
# If set to true the rc script accepts an arbitrary number of parameters. If set to false, it only accepts one.
, flexibleParameters ? false
# Specifies which feature the script requires. This is used by the rc init system to determine the proper activation order
, requires ? []
# Specifies which features the script provides. By default, it simply considers it name a feature.
, provides ? [ "${name}" ]
# Keywords to be display in the comments section
, keywords ? []
# A list of bsd rc scripts that this script depends on
, dependencies ? []
# Specifies which groups and users that need to be created.
, credentials ? {}
}:
# TODO:
# umask
# other properties. see rc.subr manpage
assert command == null -> commands ? start && commands ? stop;
let
extraCommands = builtins.attrNames (removeAttrs commands builtinCommands);
_user = if forceDisableUserChange then null else user;
_command = if commandIsDaemon then command else "daemon";
_commandArgs = if commandIsDaemon then commandArgs else
stdenv.lib.optionals (pidFile != null) [ "-p" pidFile ]
++ [ command ]
++ commandArgs;
_requires = map (dependency: dependency.name) dependencies ++ requires;
envFile = if environment == {} then null else writeTextFile {
name = "${name}-envfile";
text = stdenv.lib.concatMapStrings (name:
let
value = builtins.getAttr name environment;
in
''${name}=${stdenv.lib.escapeShellArg value}
''
) (builtins.attrNames environment);
};
rcScript = writeTextFile {
inherit name;
executable = true;
text = ''
#!/bin/sh
''
+ stdenv.lib.optionalString (provides != []) ''
# PROVIDE: ${toString provides}
''
+ stdenv.lib.optionalString (_requires != []) ''
# REQUIRE: ${toString _requires}
''
+ stdenv.lib.optionalString (keywords != []) ''
# KEYWORD: ${toString keywords}
'' +
''
. ${rcSubr}
name="${name}"
'' + stdenv.lib.optionalString (rcvar != null) ''
rcvar=''${name}_${rcvar}
''
+ ''
load_rc_config $name
${stdenv.lib.concatMapStrings (rcvarName: ''
: ''${name}_${rcvarName}:=${toString builtins.getAttr rcvarName rcvarsDefaults}
'') (builtins.attrNames rcvarsDefaults)}
''
+ stdenv.lib.optionalString (_command != null) ''
command=${_command}
''
+ stdenv.lib.optionalString (_commandArgs != []) ''
command_args="${toString _commandArgs}"
''
+ stdenv.lib.optionalString (requiredDirs != []) ''
required_dirs="${toString requiredDirs}"
''
+ stdenv.lib.optionalString (requiredFiles != []) ''
required_files="${toString requiredFiles}"
''
+ stdenv.lib.optionalString (requiredVars != []) ''
required_vars="${toString requiredVars}"
''
+ stdenv.lib.optionalString (requiredModules != []) ''
required_modules="${toString requiredModules}"
''
+ stdenv.lib.optionalString (pidFile != null) ''
pidfile="${pidFile}"
''
+ stdenv.lib.optionalString (reloadSignal != defaultReloadSignal) ''
sig_reload="${reloadSignal}"
''
+ stdenv.lib.optionalString (stopSignal != defaultStopSignal) ''
sig_stop="${stopSignal}"
''
+ stdenv.lib.optionalString (nice != null) ''
${name}_nice=${toString nice}
''
+ stdenv.lib.optionalString (directory != null) ''
${name}_chdir=${directory}
''
+ stdenv.lib.optionalString (_user != null) ''
${name}_user=${_user}
''
+ stdenv.lib.optionalString (envFile != null) ''
${name}_env_file=${envFile}
''
+ stdenv.lib.optionalString (extraCommands != []) ''
extra_commands="${toString extraCommands}"
''
+ stdenv.lib.concatMapStrings (commandName:
let
command = builtins.getAttr commandName commands;
in
stdenv.lib.optionalString (command ? pre) ''${commandName}_precmd=''${name}_pre${commandName}
''
+ stdenv.lib.optionalString (command ? implementation) ''${commandName}_cmd=''${name}_${commandName}
''
+ stdenv.lib.optionalString (command ? post) ''${commandName}_postcmd=''${name}_post${commandName}
''
) (builtins.attrNames commands)
+ stdenv.lib.optionalString (path != []) ''
PATH="${builtins.concatStringsSep ":" (map(package: "${package}/bin") path)}:$PATH"
export PATH
''
+ "\n"
+ stdenv.lib.concatMapStrings (commandName:
let
command = builtins.getAttr commandName commands;
in
''
${stdenv.lib.optionalString (command ? pre) ''
${name}_pre${commandName}()
{
${command.pre}
}
''}
${stdenv.lib.optionalString (command ? implementation) ''
${name}_${commandName}()
{
${command.implementation}
}
''}
${stdenv.lib.optionalString (command ? post) ''
${name}_post${commandName}()
{
${command.post}
}
''}
''
) (builtins.attrNames commands)
+ ''
run_rc_command "${if flexibleParameters then "$@" else "$1"}"
'';
};
credentialsSpec = if credentials == {} || forceDisableUserChange then null else createCredentials credentials;
in
stdenv.mkDerivation {
inherit name;
buildCommand = ''
mkdir -p $out/etc/rc.d
cd $out/etc/rc.d
ln -s ${rcScript} ${name}
${stdenv.lib.optionalString (credentialsSpec != null) ''
ln -s ${credentialsSpec}/dysnomia-support $out/dysnomia-support
''}
'';
}

View File

@ -0,0 +1,12 @@
{stdenv, forceDisableUserChange}:
stdenv.mkDerivation {
name = "rc.subr";
src = /etc/rc.subr;
# Disable the limits command when we want to deploy processes as an unprivileged user
buildCommand = if forceDisableUserChange then ''
sed -e 's|limits -C $_login_class $_limits||' $src > $out
'' else ''
cp $src $out
'';
}

View File

@ -0,0 +1,30 @@
{ pkgs ? import <nixpkgs> { inherit system; }
, system ? builtins.currentSystem
, stateDir ? "/var"
, runtimeDir ? "${stateDir}/run"
, logDir ? "${stateDir}/log"
, tmpDir ? (if stateDir == "/var" then "/tmp" else "${stateDir}/tmp")
, forceDisableUserChange ? false
, exprFile
}@args:
let
processesFun = import exprFile;
processesFormalArgs = builtins.functionArgs processesFun;
processesArgs = builtins.intersectAttrs processesFormalArgs (args // {
processManager = "cygrunsrv";
});
processes = processesFun processesArgs;
in
pkgs.buildEnv {
name = "cygrunsrv-env";
paths = map (processName:
let
process = builtins.getAttr processName processes;
in
process.pkg
) (builtins.attrNames processes);
}

View File

@ -0,0 +1,112 @@
{ stdenv
, writeTextFile
# Prefix that is in front of all Windows services generated by this function
, prefix ? "nix-process-"
}:
{
# A name that identifies the process instance
name
# A more human readable name that identifies the process
, displayName ? "${prefix}${name}"
# Path to the executable to run
, path
# Command-line arguments propagated to the executable
, args ? []
# An attribute set specifying arbitrary environment variables
, environment ? {}
# Specifies whether this service needs to be automatically started or not.
# 'manual' indicates manual start, 'auto' indicates automatic start
, type ? "auto"
# Specifies as which user the process should run. If null, the user privileges will not be changed.
, user ? null
# The password of the user so that the user privileges can be changed
, password ? null
# File where the stdin should read from. null indicates that no file should be read
, stdin ? null
# File where the stdout should write to. null discards output
, stdout ? null
# File where the stderr should write to. null discards output
, stderr ? null
# The signal that needs to be sent to the process to terminate it
, terminateSignal ? "TERM"
# Indicates whether the process should be terminated on shutdown
, terminateOnShutdown ? false
# Dependencies on other Windows services. The service manager makes sure that dependencies are activated first.
, dependencies ? []
# Specifies which packages need to be in the PATH
, environmentPath ? []
}:
let
_environment = stdenv.lib.optionalAttrs (environmentPath != []) {
PATH = builtins.concatStringsSep ":" (map (package: "${package}/bin") environmentPath); # Augment path environment variable, if applicable
} // environment;
cygrunsrvConfig = writeTextFile {
name = "${prefix}${name}-cygrunsrv-params";
text = ''
--path
${path}
--disp
${displayName}
''
+ stdenv.lib.optionalString (type != "auto") ''
--type
${type}
''
+ stdenv.lib.optionalString (args != []) ''
--args
${builtins.concatStringsSep " " (map (arg: stdenv.lib.escapeShellArg arg) args)}
''
+
stdenv.lib.concatMapStrings (variableName:
let
value = builtins.getAttr variableName _environment;
in
''
--env
'${variableName}=${stdenv.lib.escape [ "'" ] (toString value)}'
'') (builtins.attrNames _environment)
+ stdenv.lib.optionalString (user != null) ''
--user
${user}
''
+ stdenv.lib.optionalString (password != null) ''
--passwd
${password}
''
+ stdenv.lib.optionalString (stdin != null) ''
--stdin
${stdin}
''
+ stdenv.lib.optionalString (stdout != null) ''
--stdout
${stdout}
''
+ stdenv.lib.optionalString (stderr != null) ''
--stderr
${stderr}
''
+ stdenv.lib.optionalString (terminateSignal != "TERM") ''
--termsig
${terminateSignal}
''
+ stdenv.lib.optionalString terminateOnShutdown ''
--shutdown
''
+ stdenv.lib.concatMapStrings (dependency: ''
--dep
${dependency.name}
'') dependencies;
};
in
stdenv.mkDerivation {
name = "${prefix}${name}";
buildCommand = ''
mkdir -p $out
ln -s ${cygrunsrvConfig} $out/${prefix}${name}-cygrunsrvparams
'';
}

View File

@ -0,0 +1,30 @@
{ pkgs ? import <nixpkgs> { inherit system; }
, system ? builtins.currentSystem
, stateDir ? "/var"
, runtimeDir ? "${stateDir}/run"
, logDir ? "${stateDir}/log"
, tmpDir ? (if stateDir == "/var" then "/tmp" else "${stateDir}/tmp")
, forceDisableUserChange ? false
, exprFile
}@args:
let
processesFun = import exprFile;
processesFormalArgs = builtins.functionArgs processesFun;
processesArgs = builtins.intersectAttrs processesFormalArgs (args // {
processManager = "launchd";
});
processes = processesFun processesArgs;
in
buildEnv {
name = "launchd";
paths = map (processName:
let
process = builtins.getAttr processName processes;
in
process.pkg
) (builtins.attrNames processes);
}

View File

@ -0,0 +1,96 @@
{ writeTextFile
, stdenv
, createCredentials
# Specifies whether user changing functionality should be disabled or not
, forceDisableUserChange ? false
# Prefix that is in front of all launchd plist files generated by this function
, prefix ? "org.nixos."
}:
{
# A name that identifies the process instance
name
# Specifies which packages need to be in the PATH
, path ? []
# Specifies which groups and users that need to be created.
, credentials ? {}
# The remaining parameters are directly translated to plist XML properties.
# Possible configuration options can be found here: https://www.launchd.info
, ...
}@args:
let
environment = stdenv.lib.optionalAttrs (path != []) {
PATH = builtins.concatStringsSep ":" (map (package: "${package}/bin") path); # Augment path environment variable, if applicable
} // stdenv.lib.mapAttrs (name: value: toString value) args.EnvironmentVariables or {}; # Convert all environment variables to strings
label = if args ? Label then args.Label else "${prefix}${name}";
properties = {
Label = label;
} // removeAttrs args ([ "name" "path" "credentials" ] ++ stdenv.lib.optional forceDisableUserChange "UserName") // stdenv.lib.optionalAttrs (environment != {}) {
EnvironmentVariables = environment;
};
attrsToPList = attrs:
"<dict>\n"
+ stdenv.lib.concatMapStrings (name:
let
value = builtins.getAttr name attrs;
in
''
<key>${name}</key>
${exprToPList value}
''
) (builtins.attrNames attrs)
+ "</dict>\n";
listToPList = list:
"<array>\n"
+ stdenv.lib.concatMapStrings (value: exprToPList value + "\n") list
+ "</array>\n";
exprToPList = expr:
let
exprType = builtins.typeOf expr;
in
if exprType == "bool" then
if expr then "<true/>" else "<false/>"
else if exprType == "int" then "<integer>${toString expr}</integer>"
else if exprType == "float" then "<real>${toString expr}</real>"
else if exprType == "string" then "<string>${expr}</string>"
else if exprType == "set" then
if stdenv.lib.isDerivation expr
then "<string>${expr}</string>"
else attrsToPList expr
else if exprType == "list" then listToPList expr
else if exprType == "null" then ""
else if exprType == "lambda" then throw "Cannot convert a lambda to a plist property"
else "${expr}";
launchdDaemonConfig = writeTextFile {
name = "${label}.plist";
text = ''
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
${exprToPList properties}
</plist>
'';
};
credentialsSpec = if credentials == {} || forceDisableUserChange then null else createCredentials credentials;
in
stdenv.mkDerivation {
inherit name;
buildCommand = ''
mkdir -p $out/Library/LaunchDaemons
ln -s ${launchdDaemonConfig} $out/Library/LaunchDaemons/${label}.plist
${stdenv.lib.optionalString (credentialsSpec != null) ''
ln -s ${credentialsSpec}/dysnomia-support $out/dysnomia-support
''}
'';
}

View File

@ -0,0 +1,33 @@
{ pkgs ? import <nixpkgs> { inherit system; }
, system ? builtins.currentSystem
, stateDir ? "/var"
, runtimeDir ? "${stateDir}/run"
, logDir ? "${stateDir}/log"
, tmpDir ? (if stateDir == "/var" then "/tmp" else "${stateDir}/tmp")
, forceDisableUserChange ? false
, exprFile
}@args:
let
processesFun = import exprFile;
processesFormalArgs = builtins.functionArgs processesFun;
processesArgs = builtins.intersectAttrs processesFormalArgs (args // {
processManager = "supervisord";
});
processes = processesFun processesArgs;
in
pkgs.buildEnv {
name = "supervisord.d";
paths = map (processName:
let
process = builtins.getAttr processName processes;
in
process.pkg
) (builtins.attrNames processes);
postBuild = ''
cp ${./supervisord.conf} $out/supervisord.conf
'';
}

View File

@ -0,0 +1,71 @@
{writeTextFile, stdenv, createCredentials, supervisor, basePackages, forceDisableUserChange ? false, runtimeDir}:
{
# A name that identifies the process instance
name
# Indicates whether we want to use the pidproxy
, useProxy ? false
# Command line instruction to execute
, command ? null
# Name of the PID file that contains the PID of the running process
, pidFile ? "${name}.pid"
# Specifies which packages need to be in the PATH
, path ? []
# An attribute set specifying arbitrary environment variables
, environment ? {}
# List of supervisord programs that this configuration depends on. This is used to derive the activation order.
, dependencies ? []
# Specifies which groups and users that need to be created.
, credentials ? {}
# The remainder of the parameters directly translate to the properties described in: http://supervisord.org/configuration.html
, ...
}@params:
let
properties = removeAttrs params ([ "name" "command" "useProxy" "pidFile" "path" "environment" "dependencies" "credentials" ] ++ stdenv.lib.optional forceDisableUserChange "user");
priority = if dependencies == [] then 1
else builtins.head (builtins.sort (a: b: a > b) (map (dependency: dependency.priority) dependencies)) + 1;
_command = (stdenv.lib.optionalString useProxy "${supervisor}/bin/pidproxy ${runtimeDir}/${pidFile} ") + command;
_environment = {
PATH = builtins.concatStringsSep ":" (map (package: "${package}/bin") (basePackages ++ path));
} // environment;
confFile = writeTextFile {
name = "${name}.conf";
text = ''
[program:${name}]
command=${_command}
priority=${toString priority}
''
+ (if _environment == {} then "" else "environment=" + stdenv.lib.concatMapStringsSep "," (name:
let
value = builtins.getAttr name _environment;
in
"${name}=\"${stdenv.lib.escape [ "\"" ] (toString value)}\""
) (builtins.attrNames _environment)) +
"\n"
+ stdenv.lib.concatMapStrings (name:
let
value = builtins.getAttr name properties;
in
''${name}=${toString value}
''
) (builtins.attrNames properties);
};
credentialsSpec = if credentials == {} || forceDisableUserChange then null else createCredentials credentials;
in
stdenv.mkDerivation {
inherit name priority;
buildCommand = ''
mkdir -p $out/conf.d
ln -s ${confFile} $out/conf.d/${name}.conf
${stdenv.lib.optionalString (credentialsSpec != null) ''
ln -s ${credentialsSpec}/dysnomia-support $out/dysnomia-support
''}
'';
}

View File

@ -0,0 +1,10 @@
[supervisord]
[include]
files=conf.d/*
[inet_http_server]
port = 127.0.0.1:9001
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

View File

@ -0,0 +1,30 @@
{ pkgs ? import <nixpkgs> { inherit system; }
, system ? builtins.currentSystem
, stateDir ? "/var"
, runtimeDir ? "${stateDir}/run"
, logDir ? "${stateDir}/log"
, tmpDir ? (if stateDir == "/var" then "/tmp" else "${stateDir}/tmp")
, forceDisableUserChange ? false
, exprFile
}@args:
let
processesFun = import exprFile;
processesFormalArgs = builtins.functionArgs processesFun;
processesArgs = builtins.intersectAttrs processesFormalArgs (args // {
processManager = "systemd";
});
processes = processesFun processesArgs;
in
pkgs.buildEnv {
name = "systemd";
paths = map (processName:
let
process = builtins.getAttr processName processes;
in
process.pkg
) (builtins.attrNames processes);
}

View File

@ -0,0 +1,117 @@
{ writeTextFile
, stdenv
, createCredentials
, basePackages
# Specifies whether user changing functionality should be disabled or not
, forceDisableUserChange ? false
# Prefix that is in front of all systemd units generated by this function
, prefix ? "nix-process-"
}:
{
# A name that identifies the process instance
name
# An attribute set specifying arbitrary environment variables
, environment ? {}
# List of supervisord services that this configuration depends on.
# These properties are translated to Wants= and After= properties to ensure
# proper activation ordering and that the dependencies are started first
, dependencies ? []
# Specifies which packages need to be in the PATH
, path ? []
# Specifies which groups and users that need to be created.
, credentials ? {}
# The remainder of the parameters directly get translated to sections and properties
# See: https://www.freedesktop.org/software/systemd/man/systemd.unit.html
# and: https://www.freedesktop.org/software/systemd/man/systemd.service.html
, ...
}@args:
let
sections = removeAttrs args [ "name" "environment" "dependencies" "path" "credentials" ];
_environment = {
PATH = builtins.concatStringsSep ":" (map (package: "${package}/bin") (basePackages ++ path));
} // environment;
generateEnvironmentVariables = environment:
stdenv.lib.concatMapStrings (name:
let
value = builtins.getAttr name _environment;
in
''Environment=${name}=${toString value}
''
) (builtins.attrNames _environment);
mapDependencies = dependencies:
if dependencies == [] then ""
else
''
Wants=${toString (map (dependency: "${dependency.name}.service") dependencies)}
After=${toString (map (dependency: "${dependency.name}.service") dependencies)}
'';
generateSection = {title, properties}:
''
[${title}]
${stdenv.lib.concatMapStrings (name:
let
value = builtins.getAttr name properties;
in
if forceDisableUserChange && (name == "User" || name == "Group") then "" else # Don't change user privileges when we force it to be disabled
''${name}=${toString value}
''
) (builtins.attrNames properties)}''
+ (if title == "Service" then generateEnvironmentVariables _environment else "")
+ (if title == "Unit" then mapDependencies dependencies else "");
generateSections = sections:
stdenv.lib.concatMapStrings (title:
let
properties = builtins.getAttr title sections;
in
generateSection {
inherit title properties;
}
) (builtins.attrNames sections);
service = writeTextFile {
name = "${name}.service";
text = ''
${generateSections sections}
${stdenv.lib.optionalString (!(sections ? Service) && _environment != {}) ''
[Service]
${generateEnvironmentVariables _environment}''}
${stdenv.lib.optionalString (!(sections ? Unit) && dependencies != []) ''
[Unit]
${mapDependencies dependencies}
''}
'';
};
credentialsSpec = if credentials == {} || forceDisableUserChange then null else createCredentials credentials;
in
stdenv.mkDerivation {
name = "${prefix}${name}";
buildCommand = ''
mkdir -p $out/etc/systemd/system
ln -s ${service} $out/etc/systemd/system/${prefix}${name}.service
${stdenv.lib.optionalString (dependencies != []) ''
mkdir -p $out/etc/systemd/system/${prefix}${name}.service.wants
${stdenv.lib.concatMapStrings (dependency: ''
ln -s ${dependency}/etc/systemd/system/${dependency.name}.service $out/etc/systemd/system/${prefix}${name}.service.wants
'') dependencies}
''}
${stdenv.lib.optionalString (credentialsSpec != null) ''
ln -s ${credentialsSpec}/dysnomia-support $out/dysnomia-support
''}
'';
}

View File

@ -0,0 +1,457 @@
A Nix and sysvinit-based process management framework
=====================================================
This sub project contains a function abstraction that makes it possible to
generate sysvinit scripts with Nix. With this function it is possible to easily
manage *process deployments* -- Nix takes care of deploying the executable in
isolation in the Nix store, and the sysvinit script is used to manage the
lifecycle of the process.
Advantages of this approach:
* Works on any Linux system with the Nix package manager installed
* Can be used for unprivileged process deployments
* No additional process dependencies required -- Nix arranges all package
dependencies, and that is all you need.
Usage
=====
This sub project has a variety of use cases.
Composing the createSystemVInitScript function
----------------------------------------------
To construct sysvinit scripts, you must first compose the
`createSystemVInitScript` function and provide it some global settings.
These global settings apply to all sysvinit scripts that are generated with it.
The following partial Nix expression shows how to compose this function with
the most common settings:
```nix
{ pkgs ? import <nixpkgs> { inherit system; }
, system ? builtins.currentSystem
, stateDir ? "/var"
, runtimeDir ? "${stateDir}/run"
, tmpDir ? (if stateDir == "/var" then "/tmp" else "${stateDir}/tmp")
, forceDisableUserChange ? false
}:
let
createSystemVInitScript = import ./create-sysvinit-script.nix {
inherit (stdenv) stdenv writeTextFile daemon;
inherit runtimeDir tmpDir forceDisableUserChange;
initFunctions = import ./init-functions.nix {
basePackages = [ pkgs.coreutils pkgs.gnused pkgs.inetutils pkgs.gnugrep pkgs.sysvinit ];
inherit (pkgs) stdenv;
inherit runtimeDir;
};
createCredentials = import ./create-credentials.nix {
inherit (pkgs) stdenv;
};
};
in
...
```
In the above Nix code fragment, we provide the following global configuration
settings:
* We need to provide a number of mandatory package dependencies, such as
`stdenv`, `writeTextFile` and `daemon`.
* `runtimeDir` specifies the location where all PID files reside
* `tmpDir` specifies the location of the temp directory
* `forceDisableUserChange` globally disables user switches. For production
environments, this should be disabled so that processes can run more securely
as an unprivileged user. For development environments it may be useful to
enable this feature so that you manage all processes without having super user
privileges.
* The `initFunctions` parameter refers to a function invocation that deploys
a package with the `init-functions` script. This script provides standard
LSB functionality to manage processes.
* The `createCredentials` composes the function that can be used to configure
a Dysnomia configuration file so that users and groups can be created on
activation and discarded on deactivation.
In addition to the common settings shown above, there are also a number of
unconventional parameters. For common use scenarios, the default values suffice.
You only need to adjust them in special circumstances:
* `initialInstructions` specify the global initial instructions added to any
sysvinit script.
* `startDaemon` specifies the command-line instruction to start a daemon.
* `startProcessAsDaemon` specifies the command-line instruction to start a
foreground process as a daemon.
* `startDaemon` specifies the command-line instruction to stop a daemon.
* `reloadDaemon` specifies the command-line instruction to reload a daemon.
* `evaluateCommand` specifies the command-line instruction that displays the
status of the previously executed shell instruction.
* `statusCommand` specifies the command that retrieves the status of the daemon
* `restartActivity` specifies the implementation of the restart activity, that
is common to all process-oriented sysvinit scripts.
* `supportedRunLevels` referts to a list that iterates all supported runlevels.
* `minSequence` specifies the minimum start sequence number.
* `maxSequence` specifies the maximum start sequence number.
Creating a sysvinit script
--------------------------
After composing the `createSystemVInitScript` function, we can write Nix
expressions that build sysvinit scripts.
### Specifying activities
The following Nix expression is a straight forward example demonstrating how we
can manage the Nginx web server:
```nix
{createSystemVInitScript, nginx, configFile, stateDir}:
createSystemVInitScript {
name = "nginx";
description = "Nginx";
activities = {
start = ''
mkdir -p ${stateDir}/logs
log_info_msg "Starting Nginx..."
loadproc ${nginx}/bin/nginx -c ${configFile} -p ${stateDir}
evaluate_retval
'';
stop = ''
log_info_msg "Stopping Nginx..."
killproc ${nginx}/bin/nginx
evaluate_retval
'';
reload = ''
log_info_msg "Reloading Nginx..."
killproc ${nginx}/bin/nginx -HUP
evaluate_retval
'';
restart = ''
$0 stop
sleep 1
$0 start
'';
status = "statusproc ${nginx}/bin/nginx";
};
runlevels = [ 3 4 5 ];
}
```
The above Nix expression composes a sysvinit script to manage the life-cycle of
the Nginx server:
* The `name` parameter specifies the name of the sysvinit script
* The `description` specifies the description field shown in the meta
information section.
* The `activities` parameter refers to an attribute set that specifies the
implementation of each activity in bash code.
* The `runlevels` parameter specifies in which runlevels we want to start this
script. It will automatically compose symlinks with the appropriate start
sequence numbers in rc.d directories for the corresponding runlevels. An
implication is that this function will automatically compose stop rc.d
symlinks for the remaining runlevels.
It will stop sysvinit scripts in exactly the opposite of the start order.
### Specifying instructions
Many sysvinit scripts implement activities that consist of a description line,
followed by a command, followed by displaying the status, e.g.:
```bash
log_info_msg "Starting Nginx..."
loadproc ${nginx}/bin/nginx -c ${configFile} -p ${stateDir}
evaluate_retval
```
It is possible to reduce this boilerplate code by using the instructions
facility:
```nix
{createSystemVInitScript, nginx, configFile, stateDir}:
createSystemVInitScript {
name = "nginx";
description = "Nginx";
instructions = {
start = {
activity = "Starting";
instruction = ''
mkdir -p ${stateDir}/logs
loadproc ${nginx}/bin/nginx -c ${configFile} -p ${stateDir}
'';
};
stop = {
activity = "Stopping";
instruction = "killproc ${nginx}/bin/nginx";
};
reload = {
activity = "Reloading";
instruction = "killproc ${nginx}/bin/nginx -HUP";
};
};
activities = {
status = "statusproc ${nginx}/bin/nginx";
};
runlevels = [ 3 4 5 ];
}
```
In the above Nix expression we have replaced the `start`, `stop`, and `reload`
reload activities, with `instructions`. For these instructions, the description
line is automatically derived from the `description` parameter and augmented with
the instruction displaying the status.
### Specifying daemons to manage
It is possible to reduce the amount of boilerplate code even further --
in many scenarios we want to manage a process. The kind of activities that you
need are typically the same -- `start`, `stop`, `reload` (if applicable),
`status` and `restart`:
```nix
{createSystemVInitScript, nginx, configFile, stateDir}:
createSystemVInitScript {
name = "nginx";
description = "Nginx";
initialize = ''
mkdir -p ${stateDir}/logs
'';
process = "${nginx}/bin/nginx";
args = [ "-c" configFile "-p" stateDir ];
runlevels = [ 3 4 5 ];
}
```
In the above example, we do not specify any activities or instructions.
Instead, we specify the `process` we want to run and the command-line
instructions (`args`). From these properties, the generator will automatically
generate all relevant activities.
### Managing foreground processes
```nix
{createSystemVInitScript, port ? 5000}:
let
webapp = (import ./webapp {}).package;
in
createSystemVInitScript {
name = "webapp";
process = "${webapp}/lib/node_modules/webapp/app.js";
processIsDaemon = false;
runlevels = [ 3 4 5 ];
environment = {
PORT = port;
};
}
```
By default, sysvinit scripts expect processes to daemonize -- a process forks
another process that keeps running in the background and then the parent process
terminates. Most common system software, e.g. web servers, DBMS servers,
provides this kind of functionality.
Application software, however, that are typically implemented in more
"higher-level languages" than C, do not have this ability out of the box.
It is also possible to invoke libslack's
[daemon](http://www.libslack.org/daemon/) command to let a foreground process
daemonize -- when setting `processIsDaemon` to `false` (the default is: `true`),
the generator will automatically invoke the `daemon` command to accomplish this.
The `environment` parameter can be used to set additional environment variables.
In the above example, it is used to specify to which TCP port the process should
bind to.
### Specifying process dependencies
Processes may also communicate with other processes by using some kind of IPC
mechanism, such as Unix domain sockets. In such cases, a process might depend on
the activation of another process.
For example, the `nginx` server could act as a reverse proxy for the `webapp`.
This means that the `webapp` process should be activated first, otherwise users
might get a 502 bad gateway error.
We can augment the Nginx reverse proxy with a dependency parameter:
```nix
{createSystemVInitScript, nginx, configFile, stateDir, webapp}:
createSystemVInitScript {
name = "nginx";
description = "Nginx";
initialize = ''
mkdir -p ${stateDir}/logs
'';
process = "${nginx}/bin/nginx";
args = [ "-c" configFile "-p" stateDir ];
runlevels = [ 3 4 5 ];
dependencies = [ webapp ];
}
```
The above example passed the `webapp` process as a dependency (through the
`dependencies` parameter) to the Nginx process. The generator makes sure that
the Nginx process gets a higher start sequence number and a lower stop sequence
number than the `webapp` process, ensuring that it starts in the right order
and stops in the reverse order.
### Managing multiple process instances
In addition to deploying single instances of processes, it is also possible to
have multiple instances of processes. For example, the Nginx reverse proxy can
forward incoming requests to multiple instances of the `webapp`.
sysvinit scripts use PID files to control daemons. Normally, PID files have the
same name as the executable. When running multiple instances, we must make sure
that every instance gets a unique PID, otherwise all instances might get
killed.
We can adjust the Nix expression of `webapp` with an `instanceName` parameter:
```nix
{createSystemVInitScript}:
{instanceSuffix ? "", port ? 5000}:
let
webapp = (import ./webapp {}).package;
instanceName = "webapp${instanceSuffix}";
in
createSystemVInitScript {
name = instanceName;
inherit instanceName;
process = "${webapp}/lib/node_modules/webapp/app.js";
processIsDaemon = false;
runlevels = [ 3 4 5 ];
environment = {
PORT = port;
};
}
```
In the above example, we define a nested function in which the outer function
header (first line) refers to configuration properties applying to all process
instances.
The inner function header (second line) refers to instance properties:
* `instanceSuffix` can be used to construct a unique name for each `webapp`
instance called in a variable called `instanceName`.
* `port` is a parameter that specifies to which TCP port the webapp should
listen to. This port value should be unique for each `webapp` instance.
The `instanceName` parameter instructs the sysvinit script generator to create
a unique PID file for each running instance making it possible to control
instances individually.
### Managing user credentials
When deploying processes as system administrator, it is typically
insecure/unsafe to spawn processes as a root user.
The following expression instructs the generator to run the `webapp` as a unique
unprivileged user:
```nix
{createSystemVInitScript}:
{port, instanceSuffix ? ""}:
let
webapp = (import ./webapp {}).package;
instanceName = "webapp${instanceSuffix}";
in
createSystemVInitScript {
name = instanceName;
inherit instanceName;
process = "${webapp}/lib/node_modules/webapp/app.js";
processIsDaemon = false;
runlevels = [ 3 4 5 ];
environment = {
PORT = port;
};
user = instanceName;
credentials = {
groups = {
"${instanceName}" = {};
};
users = {
"${instanceName}" = {
group = instanceName;
description = "Webapp";
};
};
};
}
```
In the above example, the `credentials` parameter generates a configuration file
that Dysnomia can use to create or discard groups and users. The `user` parameter
instructs the script to switch privileges to the given user.
### Disable user switching
For production deployments, switching user privileges is useful, but for
experimentation as an unprivileged user it is not.
It is also possible to globally disable user switching, by setting the
`forceDisableUserChange` to `true` in the example that composes the
`createSystemVInitScript` function.
### Other settings
The `createSystemVInitScript` has a variety of other configuration properties
not shown here, such as:
* Specifying the `umask` for default file permissions
* Specifying the nice level of the process with `nice`
* Change the current working directory with `directory`
* Adding additional packages to the sysvinit script's PATH, via `paths`
* Removing generated sysvinit script activities with `removeActivities`
Deploying a collection of processes
-----------------------------------
We can use the `nixproc-sysvinit-switch` command to build and activate all
processes in a processes expression:
```bash
$ nixproc-sysvinit-switch processes.nix
```
The `nixproc-sysvinit-switch` command will activate the processes in the right
order (this means it will ensure that `webapp` gets activated before `nginx`).
If we have already deployed a collection of processes then it does a comparison
with the previous deployment, and only deactivates obsolete processes and
activates new processes:
```bash
$ nixproc-sysvinit-switch processes.nix
```
In the above case, `rcswitch` will only deactivate obsolete processes and
activate new processes, making redeployments significantly faster.
Running activities on a collection of processes
-----------------------------------------------
In addition to deploying a collection of processes or upgrading a collection of
processes, it is also possible to run a certain activity on all sysvinit
scripts in the last deployed Nix profile:
```bash
$ nixproc-sysvinit-runactivity status
```
The above command will show the statuses of all processes in the provided Nix
profile.
By default, `nixproc-sysvinit-runactivity` will examine scripts in start
activation order.
We can also specify that we want to examine all scripts in the reverse order.
This is particularly useful to stop all services without breaking process
dependencies:
```bash
$ nixproc-sysvinit-runactivity -r stop
```

View File

@ -0,0 +1,30 @@
{ pkgs ? import <nixpkgs> { inherit system; }
, system ? builtins.currentSystem
, stateDir ? "/var"
, runtimeDir ? "${stateDir}/run"
, logDir ? "${stateDir}/log"
, tmpDir ? (if stateDir == "/var" then "/tmp" else "${stateDir}/tmp")
, forceDisableUserChange ? false
, exprFile
}@args:
let
processesFun = import exprFile;
processesFormalArgs = builtins.functionArgs processesFun;
processesArgs = builtins.intersectAttrs processesFormalArgs (args // {
processManager = "sysvinit";
});
processes = processesFun processesArgs;
in
pkgs.buildEnv {
name = "rc.d";
paths = map (processName:
let
process = builtins.getAttr processName processes;
in
process.pkg
) (builtins.attrNames processes);
}

View File

@ -0,0 +1,247 @@
{ stdenv
, writeTextFile
, daemon
, initFunctions
, createCredentials
# Instructions that are carried out before anything else gets executed
, initialInstructions ? ". ${initFunctions}"
# Command that specifies how to start a daemon
, startDaemon ? "start_daemon"
# Path to the executable that daemonizes a foreground process
, startProcessAsDaemon ? "${daemon}/bin/daemon"
# Command that specifies how to stop a daemon
, stopDaemon ? "killproc"
# Command that specifies how to reload a daemon
, reloadDaemon ? "killproc"
# Command that evaluates the return value of the previous command
, evaluateCommand ? "evaluate_retval"
# Command that shows the status of a daemon
, statusCommand ? "statusproc"
# Default run time directory where PID files are stored
, runtimeDir ? "/var/run"
# Directory in which temp files are stored
, tmpDir ? "/tmp"
# Specifies the implementation of the restart activity that is used for all sysvinit scripts. Typically, there is little reason to change it.
, restartActivity ? ''
$0 stop
sleep 1
$0 start
''
# Specifies which runlevels are supported
, supportedRunlevels ? stdenv.lib.range 0 6
# The minimum start/stop sequence number
, minSequence ? 0
# The maximum start/stop sequence number
, maxSequence ? 99
# Specifies whether user changing functionality should be disabled or not
, forceDisableUserChange ? false
}:
{
# A name that identifies the process instance
name
# A name that uniquely identifies each process instance. It is used to generate a unique PID file.
, instanceName ? null
# A description that is added to the comments section
, description ? name
# Global instructions that are executed before any activity gets executed
, globalInstructions ? ""
# If not null, the umask will be changed before executing any activities
, umask ? null
# If not null, the nice level be changed before executing any activities
, nice ? null
# If not null, the current working directory will be changed before executing any activities
, directory ? null
# Specifies which packages need to be in the PATH
, path ? []
# An attribute set specifying arbitrary environment variables
, environment ? {}
# Shell instructions that specify how the state of the process should be initialized
, initialize ? ""
# A high level property to specify what process needs to be managed. From this property, the start, stop, reload, status and restart activities are derived.
, process ? null
# Specifies that the process is a daemon. If a process is not a daemon, then the generator will automatically daemonize it.
, processIsDaemon ? true
# Command-line arguments passed to the process.
, args ? []
# Path to a PID file that the system should use to manage the process. If null, it will use a default path.
, pidFile ? (if instanceName == null then null else if user == null || user == "root" || forceDisableUserChange then "${runtimeDir}/${instanceName}.pid" else "${tmpDir}/${instanceName}.pid")
# Specifies as which user the process should run. If null, the user privileges will not be changed.
, user ? null
# Specifies which signal should be send to the process to reload its configuration
, reloadSignal ? "-HUP"
# Specifies arbitrary instructions to carry out. Before each instruction the activity will be printed, and after the execution it will evaluate the return status
, instructions ? {}
# Specifies the raw implementation of each activity.
, activities ? {}
# Specifies activities to remove from the generated activities attribute set
, removeActivities ? []
# Specifies in which runlevels the script should be started. From this value, the script will automatically be stopped in the remaining runlevels
, runlevels ? []
# Specifies in which runlevels the script should be started.
, defaultStart ? []
# Specifies in which runlevels the script should be stopped.
, defaultStop ? []
# A list of sysvinit scripts that this scripts depends on. This is used to automatically derive the start and stop sequence. Dependencies will be started first and stopped last.
, dependencies ? []
# Specifies which groups and users that need to be created.
, credentials ? {}
}:
let
# Enumerates the activities in a logical order -- the common activities first, then the remaining activities in alphabetical order
enumerateActivities = activities:
stdenv.lib.optional (activities ? start) "start"
++ stdenv.lib.optional (activities ? stop) "stop"
++ stdenv.lib.optional (activities ? reload) "reload"
++ stdenv.lib.optional (activities ? restart) "restart"
++ stdenv.lib.optional (activities ? status) "status"
++ builtins.filter (activityName: activityName != "start" && activityName != "stop" && activityName != "reload" && activityName != "restart" && activityName != "status" && activityName != "*") (builtins.attrNames activities)
++ stdenv.lib.optional (activities ? "*") "*";
_user = if forceDisableUserChange then null else user;
_instructions = (stdenv.lib.optionalAttrs (process != null) {
start = {
activity = "Starting";
instruction =
initialize +
(if processIsDaemon then "${startDaemon} ${stdenv.lib.optionalString (pidFile != null) "-f -p ${pidFile}"} ${stdenv.lib.optionalString (nice != null) "-n ${nice}"} ${stdenv.lib.optionalString (_user != null) "su ${_user} -c '"} ${process} ${toString args} ${stdenv.lib.optionalString (_user != null) "'"}"
else "${startProcessAsDaemon} -U -i ${if pidFile == null then "-P ${runtimeDir} -n $(basename ${process})" else "-F ${pidFile}"} ${stdenv.lib.optionalString (_user != null) "-u ${_user}"} -- ${process} ${toString args}");
};
stop = {
activity = "Stopping";
instruction = "${stopDaemon} ${stdenv.lib.optionalString (pidFile != null) "-p ${pidFile}"} ${process}";
};
reload = {
activity = "Reloading";
instruction = "${reloadDaemon} ${stdenv.lib.optionalString (pidFile != null) "-p ${pidFile}"} ${process} ${reloadSignal}";
};
}) // instructions;
_activities =
let
convertedInstructions = stdenv.lib.mapAttrs (name: instruction:
''
log_info_msg "${instruction.activity} ${description}..."
${instruction.instruction}
${evaluateCommand}
''
) _instructions;
defaultActivities = stdenv.lib.optionalAttrs (process != null) {
status = "${statusCommand} ${stdenv.lib.optionalString (pidFile != null) "-p ${pidFile}"} ${process}";
restart = restartActivity;
} // {
"*" = ''
echo "Usage: $0 {${builtins.concatStringsSep "|" (builtins.filter (activityName: activityName != "*") (enumerateActivities _activities))}}"
exit 1
'';
};
in
removeAttrs (convertedInstructions // defaultActivities // activities) removeActivities;
_defaultStart = if runlevels != [] then stdenv.lib.intersectLists runlevels supportedRunlevels
else defaultStart;
_defaultStop = if runlevels != [] then stdenv.lib.subtractLists _defaultStart supportedRunlevels
else defaultStop;
_environment = stdenv.lib.optionalAttrs (path != []) {
PATH = "${builtins.concatStringsSep ":" (map(package: "${package}/bin" ) path)}:$PATH";
} // environment;
initdScript = writeTextFile {
inherit name;
executable = true;
text = ''
#! ${stdenv.shell}
## BEGIN INIT INFO
# Provides: ${name}
''
+ stdenv.lib.optionalString (_defaultStart != []) "# Default-Start: ${toString _defaultStart}\n"
+ stdenv.lib.optionalString (_defaultStop != []) "# Default-Stop: ${toString _defaultStop}\n"
+ stdenv.lib.optionalString (dependencies != []) ''
# Should-Start: ${toString (map (dependency: dependency.name) dependencies)}
# Should-Stop: ${toString (map (dependency: dependency.name) dependencies)}
''
+ ''
# Description: ${description}
## END INIT INFO
${initialInstructions}
${globalInstructions}
''
+ stdenv.lib.optionalString (umask != null) ''
umask ${umask}
''
+ stdenv.lib.optionalString (directory != null) ''
cd ${directory}
''
+ stdenv.lib.concatMapStrings (name:
let
value = builtins.getAttr name _environment;
in
''
export ${name}=${stdenv.lib.escapeShellArg value}
''
) (builtins.attrNames _environment)
+ ''
case "$1" in
${stdenv.lib.concatMapStrings (activityName:
let
instructions = builtins.getAttr activityName _activities;
in
''
${activityName})
${instructions}
;;
''
) (enumerateActivities _activities)}
esac
'';
};
startSequenceNumber =
if dependencies == [] then minSequence
else builtins.head (builtins.sort (a: b: a > b) (map (dependency: dependency.sequence) dependencies)) + 1;
stopSequenceNumber = maxSequence - startSequenceNumber + minSequence;
sequenceNumberToString = number:
if number < 10 then "0${toString number}"
else toString number;
credentialsSpec = if credentials == {} || forceDisableUserChange then null else createCredentials credentials;
in
stdenv.mkDerivation {
inherit name;
sequence = startSequenceNumber;
buildCommand = ''
mkdir -p $out/etc/rc.d
cd $out/etc/rc.d
mkdir -p init.d
ln -s ${initdScript} init.d/${name}
${stdenv.lib.concatMapStrings (runlevel: ''
mkdir -p rc${toString runlevel}.d
ln -s ../init.d/${name} rc${toString runlevel}.d/S${sequenceNumberToString startSequenceNumber}${name}
'') _defaultStart}
${stdenv.lib.concatMapStrings (runlevel: ''
mkdir -p rc${toString runlevel}.d
ln -s ../init.d/${name} rc${toString runlevel}.d/K${sequenceNumberToString stopSequenceNumber}${name}
'') _defaultStop}
${stdenv.lib.optionalString (credentialsSpec != null) ''
ln -s ${credentialsSpec}/dysnomia-support $out/dysnomia-support
''}
'';
}

View File

@ -0,0 +1,26 @@
{stdenv, fetchurl, basePackages, runtimeDir}:
let
basePath = builtins.concatStringsSep ":" (map (package: "${package}/bin") basePackages);
src = fetchurl {
url = http://www.linuxfromscratch.org/lfs/downloads/9.0/lfs-bootscripts-20190524.tar.xz;
sha256 = "0975wmghhh7j5qify0m170ba2d7vl0km7sw05kclnmwpgivimb38";
};
in
stdenv.mkDerivation {
name = "init-functions";
buildCommand = ''
tar xfv ${src} lfs-bootscripts-20190524/lfs/lib/services/init-functions
sed \
-e "s|/bin:/usr/bin:/sbin:/usr/sbin|${basePath}|" \
-e "s|/bin/sh|${stdenv.shell}|" \
-e "s|/bin/echo|echo|" \
-e "s|/bin/head|head|" \
-e "s|/var/run|${runtimeDir}|" \
-e "s|/run/bootlog|${runtimeDir}/bootlog|" \
lfs-bootscripts-20190524/lfs/lib/services/init-functions > $out
'';
}

21
tools/bsdrc/default.nix Normal file
View File

@ -0,0 +1,21 @@
{stdenv, getopt}:
stdenv.mkDerivation {
name = "nixproc-bsdrc-tools";
buildCommand = ''
mkdir -p $out/bin
sed -e "s|/bin/bash|$SHELL|" \
-e "s|@getopt@|${getopt}/bin/getopt|" \
-e "s|@sed@|$(type -p sed)|" \
-e "s|@commonchecks@|${../commonchecks}|" \
${./nixproc-bsdrc-switch.in} > $out/bin/nixproc-bsdrc-switch
chmod +x $out/bin/nixproc-bsdrc-switch
sed -e "s|/bin/bash|$SHELL|" \
-e "s|@getopt@|${getopt}/bin/getopt|" \
-e "s|@commonchecks@|${../commonchecks}|" \
${./nixproc-bsdrc-runactivity.in} > $out/bin/nixproc-bsdrc-runactivity
chmod +x $out/bin/nixproc-bsdrc-runactivity
'';
}

View File

@ -0,0 +1,91 @@
#!/bin/bash
set -e
shopt -s nullglob
# Shows the usage of this command to the user
showUsage()
{
cat <<EOF
Usage: $0 [OPTION] ACTIVITY [PATH]
This command runs a deployment activity on all BSD rc scripts in a Nix profile.
The scripts are traversed in the right dependency order.
Options:
-p, --profile=NAME Name of the Nix profile that stores the sysvinit scripts
(defaults to: processes)
-r, --reverse Traverse the sysvinit scripts in the reverse order
-h, --help Shows the usage of this command
Environment:
NIX_STATE_DIR Overrides the location of the Nix state directory
EOF
}
# Parse valid argument options
PARAMS=`@getopt@ -n $0 -o p:rh -l profile:,reverse,help -- "$@"`
if [ $? != 0 ]
then
showUsage
exit 1
fi
# Evaluate valid options
eval set -- "$PARAMS"
while [ "$1" != "--" ]
do
case "$1" in
-p|--profile)
profile="$2"
;;
-r|--reverse)
reverse=1
;;
-h|--help)
showUsage
exit 0
;;
esac
shift
done
shift
activity="$1"
# Validate the given options
if [ "$activity" = "" ]
then
echo "No activity specified!" >&2
exit 1
fi
source @commonchecks@
checkNixStateDir
checkProfile
composeOldProfilePath
# Execute the activities
rcpath="$oldProfilePath/etc/rc.d"
if [ "$reverse" = "1" ]
then
for i in $(rcorder $rcpath/* | tail -r)
do
$i $activity
done
else
for i in $(rcorder $rcpath/*)
do
$i $activity
done
fi

View File

@ -0,0 +1,197 @@
#!/bin/bash
set -e
shopt -s nullglob
# Shows the usage of this command to the user
showUsage()
{
cat <<EOF
Usage: $0 [OPTION] PATH
This command starts all BSD rc scripts in the provided Nix profile and
optionally deactivates all obsolete sysvinit scripts in the previous Nix
profile generation.
If the provided path is a file then it is considered a Nix expression that
produces a Nix profile. If the provided path is a directory, then it is
considered a pre-built Nix profile.
Options:
-p, --profile=NAME Name of the Nix profile that stores the BSD rc scripts
(defaults to: processes)
-o, --old-profile=PATH
Path to the previously deployed Nix profile (by default,
it gets auto detected)
--enable-at-boot Configures the rc scripts so that they are started at
boot
--state-dir Changes the directory in which the state of the
processes are stored
--runtime-dir Changes the directory in which the PID files are stored
--log-dir Changes the directory in which the log files are stored
--tmp-dir Changes the directory in which temp files are stored
--force-disable-user-change
Forces to not create users, groups or change user
permissions
--show-trace Shows a trace of the output
-h, --help Shows the usage of this command
Environment:
NIX_STATE_DIR Overrides the location of the Nix state directory
BSDRC_TARGET_DIR Directory in which the BSD rc scripts reside (defaults to:
/usr/local/etc/rc.d)
NIXPROC_STATE_DIR Changes the directory in which the state of the
processes is stored
NIXPROC_RUNTIME_DIR Changes the directory in which the PID files are stored
NIXPROC_LOG_DIR Changes the directory in which log files are stored
NIXPROC_TMP_DIR Changes the directory in which temp files are stored
NIXPROC_FORCE_DISABLE_USER_CHANGE
Forces to not create users, groups or change user
permissions
EOF
}
# Parse valid argument options
PARAMS=`@getopt@ -n $0 -o p:o:h -l profile:,old-profile:,enable-at-boot,state-dir:,runtime-dir:,log-dir:,tmp-dir:,force-disable-user-change,show-trace,help -- "$@"`
if [ $? != 0 ]
then
showUsage
exit 1
fi
# Evaluate valid options
eval set -- "$PARAMS"
while [ "$1" != "--" ]
do
case "$1" in
-p|--profile)
profile="$2"
;;
-o|--old-profile)
oldProfilePath="$2"
;;
--enable-at-boot)
enableAtBoot=1
;;
--state-dir)
stateDirArg="--state-dir $2"
;;
--runtime-dir)
runtimeDirArg="--runtime-dir $2"
;;
--log-dir)
logDirArg="--log-dir $2"
;;
--tmp-dir)
tmpDirArg="--tmp-dir $2"
;;
--force-disable-user-change)
forceDisableUserChangeArg="--force-disable-user-change"
;;
--show-trace)
showTraceArg="--show-trace"
;;
-h|--help)
showUsage
exit 0
;;
esac
shift
done
shift
path="$1"
# Validate the given options
source @commonchecks@
checkNixStateDir
checkProfile
composeOldProfilePath
BSDRC_TARGET_DIR=${BSDRC_TARGET_DIR:-/usr/local/etc/rc.d}
# Build the environment with supervisord config files
buildProfile bsdrc
rcnew="$profilePath/etc/rc.d"
rcold="$oldProfilePath/etc/rc.d"
# Determine paths of old scripts
oldscripts=()
if [ -d "$rcold" ]
then
echo "Using previous Nix profile: $rcold" >&2
for i in $(rcorder $rcold/* | tail -r)
do
currentPath=$(readlink -f $i)
oldscripts+=($currentPath)
done
fi
# Determine paths of new scripts
newscripts=()
for i in $(rcorder $rcnew/*)
do
currentPath=$(readlink -f $i)
newscripts+=($currentPath)
done
# Create new groups and users
createNewGroups
createNewUsers
# Stop and remove obsolete scripts
for i in $(rcorder $rcold/* | tail -r)
do
if ! containsElement "$(readlink -f "$i")" "${newscripts[@]}"
then
if [ "$enableAtBoot" = "1" ]
then
scriptName="$(basename $i)"
/usr/local/etc/$scriptName stop || true
@sed@ -i -e "/^${scriptName}_enabled=YES"'$'"/d" /etc/rc.conf
rm -f /usr/local/etc/$scriptName
else
"$i" onestop
fi
fi
done
# Install and start new scripts
for i in $(rcorder $rcnew/*)
do
if ! containsElement "$(readlink -f "$i")" "${oldscripts[@]}"
then
if [ "$enableAtBoot" = "1" ]
then
scriptName="$(basename $i)"
ln -sfn $rcnew/$scriptName /usr/local/etc
echo "${scriptName}_enabled=YES" >> /etc/rc.conf
/usr/local/etc/$scriptName start
else
"$i" onestart
fi
fi
done
# Delete obsolete users and groups
deleteObsoleteUsers
deleteObsoleteGroups
# Set new profile
setNixProfile

15
tools/build/default.nix Normal file
View File

@ -0,0 +1,15 @@
{stdenv, getopt}:
stdenv.mkDerivation {
name = "nixproc-build-tools";
buildCommand = ''
mkdir -p $out/bin
sed -e "s|/bin/bash|$SHELL|" \
-e "s|@getopt@|${getopt}/bin/getopt|" \
-e "s|@readlink@|$(type -p readlink)|" \
-e "s|@NIXPROC@|${../../nixproc}|" \
${./nixproc-build.in} > $out/bin/nixproc-build
chmod +x $out/bin/nixproc-build
'';
}

View File

@ -0,0 +1,138 @@
#!/bin/bash -e
# Shows the usage of this command to the user
showUsage()
{
cat <<EOF
Usage: $0 [OPTION] PATH
This command builds a Nix profile containing multiple sysvinit scripts, and
their start and stop symlinks.
Options:
-P, --process-manager=MANAGER
Process manager to build for
--state-dir Changes the directory in which the state of the processes
are stored
--runtime-dir Changes the directory in which the PID files are stored
--log-dir Changes the directory in which the log files are stored
--tmp-dir Changes the directory in which temp files are stored
--force-disable-user-change
Forces to not create users, groups or change user
permissions
--no-out-link Do not create a symlink to the output path
--show-trace Shows a trace of the output
-h, --help Shows the usage of this command
Environment:
NIXPROC_STATE_DIR Changes the directory in which the state of the
processes is stored
NIXPROC_RUNTIME_DIR Changes the directory in which the PID files are stored
NIXPROC_LOG_DIR Changes the directory in which log files are stored
NIXPROC_TMP_DIR Changes the directory in which temp files are stored
NIXPROC_FORCE_DISABLE_USER_CHANGE
Forces to not create users, groups or change user
permissions
EOF
}
# Parse valid argument options
PARAMS=`@getopt@ -n $0 -o P:h -l process-manager:,state-dir:,runtime-dir:,log-dir:,tmp-dir:,force-disable-user-change,no-out-link,show-trace,help -- "$@"`
if [ $? != 0 ]
then
showUsage
exit 1
fi
# Evaluate valid options
eval set -- "$PARAMS"
while [ "$1" != "--" ]
do
case "$1" in
-P|--process-manager)
processManager="$2"
;;
--state-dir)
stateDirArg="--argstr stateDir $2"
;;
--runtime-dir)
runtimeDirArg="--argstr runtimeDir $2"
;;
--log-dir)
logDir="--argstr logDir $2"
;;
--tmp-dir)
tmpDir="--argstr tmpDir $2"
;;
--force-disable-user-change)
forceDisableUserChangeArg="--arg forceDisableUserChange true"
;;
--no-out-link)
noOutLinkArg="--no-out-link"
;;
--show-trace)
showTraceArg="--show-trace"
;;
-h|--help)
showUsage
exit 0
;;
esac
shift
done
shift
if [ "$processManager" = "" ]
then
echo "No process manager specified!" >&2
exit 1
else
processManagerArg="--argstr processManager $processManager"
fi
if [ "$1" = "" ]
then
echo "No processes expression provided!" >&2
exit 1
else
exprFile=$(@readlink@ -f "$1")
fi
# Validate the given options
if [ "$NIXPROC_STATE_DIR" != "" ]
then
stateDirArg="--argstr stateDir $NIXPROC_STATE_DIR"
fi
if [ "$NIXPROC_RUNTIME_DIR" != "" ]
then
runtimeDirArg="--argstr stateDir $NIXPROC_RUNTIME_DIR"
fi
if [ "$NIXPROC_LOG_DIR" != "" ]
then
logDirArg="--argstr logDir $NIXPROC_LOG_DIR"
fi
if [ "$NIXPROC_TMP_DIR" != "" ]
then
tmpDirArg="--argstr tmpDir $NIXPROC_TMP_DIR"
fi
if [ "$NIXPROC_FORCE_DISABLE_USER_CHANGE" = "1" ]
then
forceDisableUserChangeArg="--arg forceDisableUserChange true"
fi
NIXPROC=${NIXPROC:-@NIXPROC@}
# Build the profile
nix-build $stateDirArg $runtimeDirArg $logDirArg $tmpDirArg $forceDisableUserChangeArg $noOutLinkArg $showTraceArg $processManagerArg --argstr exprFile "$exprFile" $NIXPROC/create-managed-process/$processManager/build-$processManager-env.nix

96
tools/commonchecks Normal file
View File

@ -0,0 +1,96 @@
#!/bin/bash
set -e
shopt -s nullglob
containsElement()
{
local element match="$1"
shift
for element
do
[[ "$element" == "$match" ]] && return 0
done
return 1
}
checkNixStateDir()
{
NIX_STATE_DIR=${NIX_STATE_DIR:-/nix/var/nix}
}
checkProfile()
{
profile=${profile:-processes}
}
buildProfile()
{
local processManager="$1"
profilePath=$(nixproc-build --process-manager $processManager $stateDirArg $runtimeDirArg $logDirArg $tmpDirArg $forceDisableUserChangeArg $showTraceArg --no-out-link "$path")
}
composeOldProfilePath()
{
if [ "$oldProfilePath" = "" ]
then
oldProfilePath="$NIX_STATE_DIR/profiles/$profile"
fi
}
setNixProfile()
{
nix-env -p "$NIX_STATE_DIR/profiles/$profile" --set "$profilePath"
}
createNewGroups()
{
for groupfile in $profilePath/dysnomia-support/groups/*
do
local groupname="$(basename $groupfile)"
if [ ! -f "$oldProfilePath/dysnomia-support/groups/$groupname" ]
then
echo dysnomia-addgroups "$profilePath/dysnomia-support/groups/$groupname"
fi
done
}
createNewUsers()
{
for userfile in $profilePath/dysnomia-support/users/*
do
local username="$(basename $userfile)"
if [ ! -f "$oldProfilePath/dysnomia-support/users/$username" ]
then
echo dysnomia-addusers "$profilePath/dysnomia-support/users/$username"
fi
done
}
deleteObsoleteUsers()
{
for userfile in $oldProfilePath/dysnomia-support/users/*
do
local username="$(basename $userfile)"
if [ ! -f "$profilePath/dysnomia-support/users/$username" ]
then
echo dysnomia-delusers "$oldProfilePath/dysnomia-support/users/$username"
fi
done
}
deleteObsoleteGroups()
{
for groupfile in $oldProfilePath/dysnomia-support/groups/*
do
local groupname="$(basename $groupfile)"
if [ ! -f "$profilePath/dysnomia-support/groups/$groupname" ]
then
echo dysnomia-delgroups "$oldProfilePath/dysnomia-support/groups/$groupname"
fi
done
}

View File

@ -0,0 +1,14 @@
{stdenv, getopt}:
stdenv.mkDerivation {
name = "nixproc-cygrunsrv-tools";
buildCommand = ''
mkdir -p $out/bin
sed -e "s|/bin/bash|$SHELL|" \
-e "s|@getopt@|${getopt}/bin/getopt|" \
-e "s|@commonchecks@|${../commonchecks}|" \
${./nixproc-cygrunsrv-switch.in} > $out/bin/nixproc-cygrunsrv-switch
chmod +x $out/bin/nixproc-cygrunsrv-switch
'';
}

View File

@ -0,0 +1,167 @@
#!/bin/bash
set -e
shopt -s nullglob
showUsage()
{
cat <<EOF
Usage: $0 [OPTION] PATH
This command updates the Windows services configuration so that obsolete
services will be stoppped and new services will be started.
Options:
-p, --profile=NAME Name of the Nix profile that stores the sysvinit scripts
(defaults to: processes)
-o, --old-profile=PATH
Path to the previously deployed Nix profile (by default,
it gets auto detected)
--state-dir Changes the directory in which the state of the
processes are stored
--runtime-dir Changes the directory in which the PID files are stored
--log-dir Changes the directory in which the log files are stored
--tmp-dir Changes the directory in which temp files are stored
--force-disable-user-change
Forces to not create users, groups or change user
permissions
--show-trace Shows a trace of the output
-h, --help Shows the usage of this command
Environment:
NIX_STATE_DIR Overrides the location of the Nix state directory
NIXPROC_STATE_DIR Changes the directory in which the state of the
processes is stored
NIXPROC_RUNTIME_DIR Changes the directory in which the PID files are stored
NIXPROC_LOG_DIR Changes the directory in which log files are stored
NIXPROC_TMP_DIR Changes the directory in which temp files are stored
NIXPROC_FORCE_DISABLE_USER_CHANGE
Forces to not create users, groups or change user
permissions
EOF
}
# Parse valid argument options
PARAMS=`@getopt@ -n $0 -o p:o:h -l profile:,old-profile:,state-dir:,runtime-dir:,log-dir:,tmp-dir:,force-disable-user-change,show-trace,help -- "$@"`
if [ $? != 0 ]
then
showUsage
exit 1
fi
# Evaluate valid options
eval set -- "$PARAMS"
while [ "$1" != "--" ]
do
case "$1" in
-p|--profile)
profile="$2"
;;
-o|--old-profile)
oldProfilePath="$2"
;;
--state-dir)
stateDirArg="--state-dir $2"
;;
--runtime-dir)
runtimeDirArg="--runtime-dir $2"
;;
--log-dir)
logDirArg="--log-dir $2"
;;
--tmp-dir)
tmpDirArg="--tmp-dir $2"
;;
--force-disable-user-change)
forceDisableUserChangeArg="--force-disable-user-change"
;;
--show-trace)
showTraceArg="--show-trace"
;;
-h|--help)
showUsage
exit 0
;;
esac
shift
done
shift
path="$1"
# Validate the given options
source @commonchecks@
checkNixStateDir
checkProfile
composeOldProfilePath
# Build the environment with supervisord config files
buildProfile cygrunsrv
# Determine paths of old param files
oldparams=()
if [ -d "$oldProfilePath" ]
then
for i in $oldProfilePath/*-cygrunsrvparams
do
currentPath=$(readlink -f "$i")
oldparams+=($currentPath)
done
fi
# Determine paths of new param files
newparams=()
for i in $profilePath/*-cygrunsrvparams
do
currentPath=$(readlink -f "$i")
newparams+=($currentPath)
done
if [ -d "$oldProfilePath" ]
then
# Stop and remove obsolete services
for i in $oldProfilePath/*-cygrunsrvparams
do
if ! containsElement "$(readlink -f "$i")" "${newparams[@]}"
then
serviceName="$(basename $i -cygrunsrvparams)"
cygrunsrv --stop $serviceName
cygrunsrv --remove $serviceName
fi
done
fi
# Set new profile
setNixProfile
# Install all services in the new configuration
for i in $profilePath/*-cygrunsrvparams
do
if ! containsElement "$(readlink -f "$i")" "${oldparams[@]}"
then
serviceName="$(basename $i -cygrunsrvparams)"
cat $i | xargs -d '\n' /bin/echo cygrunsrv --install $serviceName
fi
done
# Start all services in the new configuration
for i in $profilePath/*-cygrunsrvparams
do
if ! containsElement "$(readlink -f "$i")" "${oldparams[@]}"
then
serviceName="$(basename $i -cygrunsrvparams)"
cygrunsrv --start $serviceName
fi
done

37
tools/default.nix Normal file
View File

@ -0,0 +1,37 @@
{ pkgs ? import <nixpkgs> { inherit system; }
, system ? builtins.currentSystem
}:
rec {
build = import ./build {
inherit (pkgs) stdenv getopt;
};
generate-config = import ./generate-config {
inherit (pkgs) stdenv getopt;
};
bsdrc = import ./bsdrc {
inherit (pkgs) stdenv getopt;
};
cygrunsrv = import ./cygrunsrv {
inherit (pkgs) stdenv getopt;
};
launchd = import ./launchd {
inherit (pkgs) stdenv getopt;
};
supervisord = import ./supervisord {
inherit (pkgs) stdenv getopt;
};
systemd = import ./systemd {
inherit (pkgs) stdenv getopt;
};
sysvinit = import ./sysvinit {
inherit (pkgs) stdenv getopt;
};
}

View File

@ -0,0 +1,14 @@
{stdenv, getopt}:
stdenv.mkDerivation {
name = "nixproc-generate-config";
buildCommand = ''
mkdir -p $out/bin
sed -e "s|/bin/bash|$SHELL|" \
-e "s|@getopt@|${getopt}/bin/getopt|" \
-e "s|@NIXPROC@|${../../nixproc}|" \
${./nixproc-generate-config.in} > $out/bin/nixproc-generate-config
chmod +x $out/bin/nixproc-generate-config
'';
}

View File

@ -0,0 +1,111 @@
#!/bin/bash -e
# Shows the usage of this command to the user
showUsage()
{
cat <<EOF
Usage: $0 [OPTION] PATH
Takes a JSON representation of a managed process configuration and translates
it to a process manager specific configuration.
Options:
-P, --process-manager=MANAGER
Process manager to build for
--state-dir Changes the directory in which the state of the
processes are stored
--runtime-dir Changes the directory in which the PID files are stored
--log-dir Changes the directory in which the log files are stored
--tmp-dir Changes the directory in which temp files are stored
--force-disable-user-change
Forces to not create users, groups or change user
permissions
--no-out-link Do not create a symlink to the output path
--show-trace Shows a trace of the output
-h, --help Shows the usage of this command
Environment:
NIXPROC_STATE_DIR Changes the directory in which the state of the
processes is stored
NIXPROC_RUNTIME_DIR Changes the directory in which the PID files are stored
NIXPROC_LOG_DIR Changes the directory in which log files are stored
NIXPROC_TMP_DIR Changes the directory in which temp files are stored
NIXPROC_FORCE_DISABLE_USER_CHANGE
Forces to not create users, groups or change user
permissions
EOF
}
# Parse valid argument options
PARAMS=`@getopt@ -n $0 -o P:h -l process-manager:,state-dir:,runtime-dir:,log-dir:,tmp-dir:,force-disable-user-change,no-out-link,show-trace,help -- "$@"`
if [ $? != 0 ]
then
showUsage
exit 1
fi
# Evaluate valid options
eval set -- "$PARAMS"
while [ "$1" != "--" ]
do
case "$1" in
-P|--process-manager)
processManager="$2"
;;
--state-dir)
stateDirArg="--state-dir $2"
;;
--runtime-dir)
runtimeDirArg="--runtime-dir $2"
;;
--log-dir)
logDirArg="--log-dir $2"
;;
--tmp-dir)
tmpDirArg="--tmp-dir $2"
;;
--force-disable-user-change)
forceDisableUserChangeArg="--force-disable-user-change"
;;
--no-out-link)
noOutLinkArg="--no-out-link"
;;
--show-trace)
showTraceArg="--show-trace"
;;
-h|--help)
showUsage
exit 0
;;
esac
shift
done
shift
configFile="$1"
# Validate the given options
if [ "$configFile" = "" ]
then
echo "A config file must be provided!" >&2
exit 1
fi
if [ "$processManager" = "" ]
then
echo "No process manager was specified!" >&2
exit 1
fi
NIXPROC=${NIXPROC:-@NIXPROC@}
# Build the configuration
nix-build $NIXPROC/create-managed-process/agnostic/create-managed-process-from-config.nix --arg configFile $configFile --argstr processManager $processManager $stateDirArg $runtimeDirArg $logDirArg $tmpDirArg $forceDisableUserChangeArg $noOutLinkArg $showTraceArg

15
tools/launchd/default.nix Normal file
View File

@ -0,0 +1,15 @@
{stdenv, getopt}:
stdenv.mkDerivation {
name = "nixproc-launchd-tools";
buildCommand = ''
mkdir -p $out/bin
sed -e "s|/bin/bash|$SHELL|" \
-e "s|@getopt@|${getopt}/bin/getopt|" \
-e "s|@commonchecks@|${../commonchecks}|" \
-e "s|@readlink@|$(type -p readlink)|" \
${./nixproc-launchd-switch.in} > $out/bin/nixproc-launchd-switch
chmod +x $out/bin/nixproc-launchd-switch
'';
}

View File

@ -0,0 +1,173 @@
#!/bin/bash
set -e
shopt -s nullglob
showUsage()
{
cat <<EOF
Usage: $0 [OPTION] PATH
This command repopulates a folder with launchd plist files and updates the
configuration so that obsolete services will be stoppped and new services will
be started.
Options:
-p, --profile=NAME Name of the Nix profile that stores the sysvinit scripts
(defaults to: processes)
-o, --old-profile=PATH
Path to the previously deployed Nix profile (by default,
it gets auto detected)
--state-dir Changes the directory in which the state of the
processes are stored
--runtime-dir Changes the directory in which the PID files are stored
--log-dir Changes the directory in which the log files are stored
--tmp-dir Changes the directory in which temp files are stored
--force-disable-user-change
Forces to not create users, groups or change user
permissions
--show-trace Shows a trace of the output
-h, --help Shows the usage of this command
Environment:
NIX_STATE_DIR Overrides the location of the Nix state directory
LAUNCHD_TARGET_DIR Directory in which the plist configuration files are
managed (defaults to: /Library/LaunchDaemons)
NIXPROC_STATE_DIR Changes the directory in which the state of the
processes is stored
NIXPROC_RUNTIME_DIR Changes the directory in which the PID files are stored
NIXPROC_LOG_DIR Changes the directory in which log files are stored
NIXPROC_TMP_DIR Changes the directory in which temp files are stored
NIXPROC_FORCE_DISABLE_USER_CHANGE
Forces to not create users, groups or change user
permissions
EOF
}
# Parse valid argument options
PARAMS=`@getopt@ -n $0 -o p:o:h -l profile:,old-profile:,state-dir:,runtime-dir:,log-dir:,tmp-dir:,force-disable-user-change,show-trace,help -- "$@"`
if [ $? != 0 ]
then
showUsage
exit 1
fi
# Evaluate valid options
eval set -- "$PARAMS"
while [ "$1" != "--" ]
do
case "$1" in
-p|--profile)
profile="$2"
;;
-o|--old-profile)
oldProfilePath="$2"
;;
--state-dir)
stateDirArg="--state-dir $2"
;;
--runtime-dir)
runtimeDirArg="--runtime-dir $2"
;;
--log-dir)
logDirArg="--log-dir $2"
;;
--tmp-dir)
tmpDirArg="--tmp-dir $2"
;;
--force-disable-user-change)
forceDisableUserChangeArg="--force-disable-user-change"
;;
--show-trace)
showTraceArg="--show-trace"
;;
-h|--help)
showUsage
exit 0
;;
esac
shift
done
shift
path="$1"
# Validate the given options
LAUNCHD_TARGET_DIR=${LAUNCHD_TARGET_DIR:-/Library/LaunchDaemons}
source @commonchecks@
checkNixStateDir
checkProfile
composeOldProfilePath
# Build the environment with supervisord config files
buildProfile launchd
# Determine paths of old plists
oldplists=()
if [ -d "$oldProfilePath" ]
then
for i in $oldProfilePath/Library/LaunchDaemons/*.plist
do
currentPath=$(@readlink@ -f "$i")
oldplists+=($currentPath)
done
fi
# Determine paths of new plists
newplists=()
for i in $profilePath/Library/LaunchDaemons/*.plist
do
currentPath=$(@readlink@ -f "$i")
newplists+=($currentPath)
done
# Create new groups and users
createNewGroups
createNewUsers
if [ -d "$oldProfilePath" ]
then
# Stop and remove obsolete plists
for i in $oldProfilePath/Library/LaunchDaemons/*.plist
do
if ! containsElement "$(@readlink@ -f "$i")" "${newplists[@]}"
then
launchctl stop "$(basename "$i" .plist)"
unitTargetPath="$LAUNCHD_TARGET_DIR/$(basename "$i")"
launchctl unload "$unitTargetPath"
rm -f "$unitTargetPath"
fi
done
fi
# Start all plists in the new configuration
for i in $profilePath/Library/LaunchDaemons/*.plist
do
if ! containsElement "$(@readlink@ -f "$i")" "${oldplists[@]}"
then
unitTargetPath="$LAUNCHD_TARGET_DIR/$(basename "$i")"
cp "$(@readlink@ -f "$i")" "$unitTargetPath"
launchctl load -w "$unitTargetPath"
launchctl start "$(basename "$i" .plist)"
fi
done
# Delete obsolete users and groups
deleteObsoleteUsers
deleteObsoleteGroups
# Set new profile
setNixProfile

View File

@ -0,0 +1,32 @@
{stdenv, getopt}:
stdenv.mkDerivation {
name = "nixproc-supervisord-tools";
buildCommand = ''
mkdir -p $out/bin
sed -e "s|/bin/bash|$SHELL|" \
-e "s|@getopt@|${getopt}/bin/getopt|" \
-e "s|@readlink@|$(type -p readlink)|" \
-e "s|@commonchecks@|${../commonchecks}|" \
-e "s|@supervisordchecks@|${./supervisordchecks}|" \
${./nixproc-supervisord-start.in} > $out/bin/nixproc-supervisord-start
chmod +x $out/bin/nixproc-supervisord-start
sed -e "s|/bin/bash|$SHELL|" \
-e "s|@getopt@|${getopt}/bin/getopt|" \
-e "s|@readlink@|$(type -p readlink)|" \
-e "s|@commonchecks@|${../commonchecks}|" \
-e "s|@supervisordchecks@|${./supervisordchecks}|" \
${./nixproc-supervisord-switch.in} > $out/bin/nixproc-supervisord-switch
chmod +x $out/bin/nixproc-supervisord-switch
sed -e "s|/bin/bash|$SHELL|" \
-e "s|@getopt@|${getopt}/bin/getopt|" \
-e "s|@readlink@|$(type -p readlink)|" \
-e "s|@commonchecks@|${../commonchecks}|" \
-e "s|@supervisordchecks@|${./supervisordchecks}|" \
${./nixproc-supervisord-deploy-stateless.in} > $out/bin/nixproc-supervisord-deploy-stateless
chmod +x $out/bin/nixproc-supervisord-deploy-stateless
'';
}

View File

@ -0,0 +1,95 @@
#!/bin/bash -e
showUsage()
{
cat <<EOF
Usage: $0 [OPTION] PATH
This command starts supervisord with a provided configuration and set of
services in foreground mode. When the configuration changes, supervisord and all
services need to be restarted.
To do more efficient (but stateful) upgrades, use
\`nixproc-supervisord-switch'.
Options:
--state-dir Changes the directory in which the state of the
processes are stored
--runtime-dir Changes the directory in which the PID files are stored
--log-dir Changes the directory in which the log files are stored
--tmp-dir Changes the directory in which temp files are stored
--force-disable-user-change
Forces to not create users, groups or change user
permissions
-h, --help Shows the usage of this command
Environment:
NIXPROC_STATE_DIR Changes the directory in which the state of the
processes is stored
NIXPROC_RUNTIME_DIR Changes the directory in which the PID files are stored
NIXPROC_LOG_DIR Changes the directory in which log files are stored
NIXPROC_TMP_DIR Changes the directory in which temp files are stored
NIXPROC_FORCE_DISABLE_USER_CHANGE
Forces to not create users, groups or change user
permissions
EOF
}
# Parse valid argument options
PARAMS=`@getopt@ -n $0 -o h -l state-dir:,runtime-dir:,log-dir:,tmp-dir:,force-disable-user-change,help -- "$@"`
if [ $? != 0 ]
then
showUsage
exit 1
fi
# Evaluate valid options
eval set -- "$PARAMS"
while [ "$1" != "--" ]
do
case "$1" in
--state-dir)
stateDirArg="--state-dir $2"
;;
--runtime-dir)
runtimeDirArg="--runtime-dir $2"
;;
--log-dir)
logDirArg="--log-dir $2"
;;
--tmp-dir)
tmpDirArg="--tmp-dir $2"
;;
--force-disable-user-change)
forceDisableUserChangeArg="--force-disable-user-change"
;;
-h|--help)
showUsage
exit 0
;;
esac
shift
done
shift
path="$1"
# Build the environment with supervisord config files
profilePath=$(nixproc-build --process-manager supervisord $stateDirArg $runtimeDirArg $logDirArg $tmpDirArg $forceDisableUserChangeArg $showTraceArg --no-out-link "$path")
# Create groups and users
dysnomia-addgroups "$profilePath"
dysnomia-addusers "$profilePath"
# Start supervisord in foreground mode
supervisord -n -c "$profilePath/supervisord.conf"
# Discard groups and users
dysnomia-delusers "$profilePath"
dysnomia-delgroups "$profilePath"

View File

@ -0,0 +1,92 @@
#!/bin/bash -e
showUsage()
{
cat <<EOF
Usage: $0 [OPTION] PATH
This command starts supervisord with an empty configuration that can be
populated with services by running \`nixproc-supervisord-switch'.
Options:
--state-dir Changes the directory in which the state of the
processes are stored
--runtime-dir Changes the directory in which the PID files are stored
--log-dir Changes the directory in which the log files are stored
--tmp-dir Changes the directory in which temp files are stored
--force-disable-user-change
Forces to not create users, groups or change user
permissions
-h, --help Shows the usage of this command
Environment:
SUPERVISORD_CONF_DIR Directory if which the supervisord.conf resides
NIXPROC_STATE_DIR Changes the directory in which the state of the
processes is stored
NIXPROC_RUNTIME_DIR Changes the directory in which the PID files are stored
NIXPROC_LOG_DIR Changes the directory in which log files are stored
NIXPROC_TMP_DIR Changes the directory in which temp files are stored
NIXPROC_FORCE_DISABLE_USER_CHANGE
Forces to not create users, groups or change user
permissions
EOF
}
# Parse valid argument options
PARAMS=`@getopt@ -n $0 -o h -l state-dir:,runtime-dir:,log-dir:,tmp-dir:,force-disable-user-change,help -- "$@"`
if [ $? != 0 ]
then
showUsage
exit 1
fi
# Evaluate valid options
eval set -- "$PARAMS"
while [ "$1" != "--" ]
do
case "$1" in
--state-dir)
stateDirArg="--state-dir $2"
;;
--runtime-dir)
runtimeDirArg="--runtime-dir $2"
;;
--log-dir)
logDirArg="--log-dir $2"
;;
--tmp-dir)
tmpDirArg="--tmp-dir $2"
;;
--force-disable-user-change)
forceDisableUserChangeArg="--force-disable-user-change"
;;
-h|--help)
showUsage
exit 0
;;
esac
shift
done
shift
path="$1"
# Validate the given options
source @supervisordchecks@
checkSupervisordConfDir
# Build the environment with supervisord config files
profilePath=$(nixproc-build --process-manager supervisord $stateDirArg $runtimeDirArg $logDirArg $tmpDirArg $forceDisableUserChangeArg $showTraceArg --no-out-link "$path")
# Create the supervisord config directory and start supervisord in foreground mode
mkdir -p "$SUPERVISORD_CONF_DIR/conf.d"
cp "$profilePath/supervisord.conf" "$SUPERVISORD_CONF_DIR"
supervisord -n -c "$SUPERVISORD_CONF_DIR/supervisord.conf"

View File

@ -0,0 +1,178 @@
#!/bin/bash
set -e
shopt -s nullglob
showUsage()
{
cat <<EOF
Usage: $0 [OPTION] PATH
This command repopulates the conf.d sub directory of a supervisord configuration
and updates the live configuration so that obsolete services will be stopped and
new services will be activated.
If the provided path is a file then it is considered a Nix expression that
produces a Nix profile. If the provided path is a directory, then it is
considered a pre-built Nix profile.
Options:
-p, --profile=NAME Name of the Nix profile that stores the sysvinit scripts
(defaults to: processes)
-o, --old-profile=PATH
Path to the previously deployed Nix profile (by default,
it gets auto detected)
--state-dir Changes the directory in which the state of the
processes are stored
--runtime-dir Changes the directory in which the PID files are stored
--log-dir Changes the directory in which the log files are stored
--tmp-dir Changes the directory in which temp files are stored
--force-disable-user-change
Forces to not create users, groups or change user
permissions
--show-trace Shows a trace of the output
-h, --help Shows the usage of this command
Environment:
NIX_STATE_DIR Overrides the location of the Nix state directory
SUPERVISORD_CONF_DIR Directory if which the supervisord.conf resides
NIXPROC_STATE_DIR Changes the directory in which the state of the
processes is stored
NIXPROC_RUNTIME_DIR Changes the directory in which the PID files are stored
NIXPROC_LOG_DIR Changes the directory in which log files are stored
NIXPROC_TMP_DIR Changes the directory in which temp files are stored
NIXPROC_FORCE_DISABLE_USER_CHANGE
Forces to not create users, groups or change user
permissions
EOF
}
# Parse valid argument options
PARAMS=`@getopt@ -n $0 -o p:o:h -l profile:,old-profile:,state-dir:,runtime-dir:,log-dir:,tmp-dir:,force-disable-user-change,show-trace,help -- "$@"`
if [ $? != 0 ]
then
showUsage
exit 1
fi
# Evaluate valid options
eval set -- "$PARAMS"
while [ "$1" != "--" ]
do
case "$1" in
-p|--profile)
profile="$2"
;;
-o|--old-profile)
oldProfilePath="$2"
;;
--state-dir)
stateDirArg="--state-dir $2"
;;
--runtime-dir)
runtimeDirArg="--runtime-dir $2"
;;
--log-dir)
logDirArg="--log-dir $2"
;;
--tmp-dir)
tmpDirArg="--tmp-dir $2"
;;
--force-disable-user-change)
forceDisableUserChangeArg="--force-disable-user-change"
;;
--show-trace)
showTraceArg="--show-trace"
;;
-h|--help)
showUsage
exit 0
;;
esac
shift
done
shift
path="$1"
# Validate the given options
source @supervisordchecks@
checkSupervisordConfDir
source @commonchecks@
checkNixStateDir
checkProfile
composeOldProfilePath
# Build the environment with supervisord config files
buildProfile supervisord
# Determine paths of old units
oldunits=()
if [ -d "$oldProfilePath" ]
then
for i in $oldProfilePath/conf.d/*.conf
do
currentPath=$(@readlink@ -f "$i")
oldunits+=($currentPath)
done
fi
# Determine paths of new units
newunits=()
for i in $profilePath/conf.d/*.conf
do
currentPath=$(@readlink@ -f "$i")
newunits+=($currentPath)
done
if [ -d "$oldProfilePath" ]
then
# Remove obsolete units
for i in $oldProfilePath/conf.d/*.conf
do
if ! containsElement "$(@readlink@ -f "$i")" "${newunits[@]}"
then
unitTargetPath="$SUPERVISORD_CONF_DIR/conf.d/$(basename "$i")"
rm -f "$unitTargetPath"
fi
done
fi
# Add new units
for i in $profilePath/conf.d/*.conf
do
if ! containsElement "$(@readlink@ -f "$i")" "${oldunits[@]}"
then
ln -sfn "$(@readlink@ -f "$i")" $SUPERVISORD_CONF_DIR/conf.d/$(basename "$i")
fi
done
# Create new groups and users
createNewGroups
createNewUsers
# Reload and update the supervisord configuration
supervisorctl reread
supervisorctl update
# Delete obsolete users and groups
deleteObsoleteUsers
deleteObsoleteGroups
# Set new profile
setNixProfile

View File

@ -0,0 +1,10 @@
#!/bin/bash -e
checkSupervisordConfDir()
{
if [ "$SUPERVISORD_CONF_DIR" = "" ]
then
echo "Please set SUPERVISORD_CONF_DIR to the directory where the supervisord.conf configuration resides!" >&2
exit 1
fi
}

14
tools/systemd/default.nix Normal file
View File

@ -0,0 +1,14 @@
{stdenv, getopt}:
stdenv.mkDerivation {
name = "nixproc-systemd-tools";
buildCommand = ''
mkdir -p $out/bin
sed -e "s|/bin/bash|$SHELL|" \
-e "s|@getopt@|${getopt}/bin/getopt|" \
-e "s|@commonchecks@|${../commonchecks}|" \
${./nixproc-systemd-switch.in} > $out/bin/nixproc-systemd-switch
chmod +x $out/bin/nixproc-systemd-switch
'';
}

View File

@ -0,0 +1,198 @@
#!/bin/bash
set -e
shopt -s nullglob
showUsage()
{
cat <<EOF
Usage: $0 [OPTION] PATH
This command repopulates a folder with systemd configuration files and updates
the configuration so that obsolete services will be stoppped and new services
will be started.
Options:
-p, --profile=NAME Name of the Nix profile that stores the sysvinit scripts
(defaults to: processes)
-o, --old-profile=PATH
Path to the previously deployed Nix profile (by default,
it gets auto detected)
--state-dir Changes the directory in which the state of the
processes are stored
--runtime-dir Changes the directory in which the PID files are stored
--log-dir Changes the directory in which the log files are stored
--tmp-dir Changes the directory in which temp files are stored
--force-disable-user-change
Forces to not create users, groups or change user
permissions
--show-trace Shows a trace of the output
-h, --help Shows the usage of this command
Environment:
NIX_STATE_DIR Overrides the location of the Nix state directory
SYSTEMD_TARGET_DIR Directory in which the unit configuration files are
managed (defaults to: /etc/systemd/system)
NIXPROC_STATE_DIR Changes the directory in which the state of the
processes is stored
NIXPROC_RUNTIME_DIR Changes the directory in which the PID files are stored
NIXPROC_LOG_DIR Changes the directory in which log files are stored
NIXPROC_TMP_DIR Changes the directory in which temp files are stored
NIXPROC_FORCE_DISABLE_USER_CHANGE
Forces to not create users, groups or change user
permissions
EOF
}
# Parse valid argument options
PARAMS=`@getopt@ -n $0 -o p:o:h -l profile:,old-profile:,state-dir:,runtime-dir:,log-dir:,tmp-dir:,force-disable-user-change,show-trace,help -- "$@"`
if [ $? != 0 ]
then
showUsage
exit 1
fi
# Evaluate valid options
eval set -- "$PARAMS"
while [ "$1" != "--" ]
do
case "$1" in
-p|--profile)
profile="$2"
;;
-o|--old-profile)
oldProfilePath="$2"
;;
--state-dir)
stateDirArg="--state-dir $2"
;;
--runtime-dir)
runtimeDirArg="--runtime-dir $2"
;;
--log-dir)
logDirArg="--log-dir $2"
;;
--tmp-dir)
tmpDirArg="--tmp-dir $2"
;;
--force-disable-user-change)
forceDisableUserChangeArg="--force-disable-user-change"
;;
--show-trace)
showTraceArg="--show-trace"
;;
-h|--help)
showUsage
exit 0
;;
esac
shift
done
shift
path="$1"
# Validate the given options
SYSTEMD_TARGET_DIR=${SYSTEMD_TARGET_DIR:-/etc/systemd/system}
source @commonchecks@
checkNixStateDir
checkProfile
composeOldProfilePath
# Build the environment with supervisord config files
buildProfile systemd
# Determine paths of old units
oldunits=()
if [ -d "$oldProfilePath" ]
then
for i in $oldProfilePath/etc/systemd/system/*.service
do
currentPath=$(readlink -f "$i")
oldunits+=($currentPath)
done
fi
# Determine paths of new units
newunits=()
for i in $profilePath/etc/systemd/system/*.service
do
currentPath=$(readlink -f "$i")
newunits+=($currentPath)
done
# Create new groups and users
createNewGroups
createNewUsers
if [ "$oldProfilePath" != "" ]
then
# Stop obsolete units
for i in $oldProfilePath/etc/systemd/system/*.service
do
if ! containsElement "$(readlink -f "$i")" "${newunits[@]}"
then
systemctl stop "$(basename "$i")"
fi
done
# Remove obsolete units
for i in $oldProfilePath/etc/systemd/system/*.service
do
if ! containsElement "$(readlink -f "$i")" "${newunits[@]}"
then
unitTargetPath="$SYSTEMD_TARGET_DIR/$(basename "$i")"
rm -f "$unitTargetPath"
if [ -d "$i.wants" ]
then
rm -f "$unitTargetPath.wants"
fi
fi
done
fi
# Add new units
for i in $profilePath/etc/systemd/system/*.service
do
if ! containsElement "$(readlink -f "$i")" "${oldunits[@]}"
then
if [ -d "$i.wants" ]
then
ln -sfn "$(readlink -f "$i.wants")" $SYSTEMD_TARGET_DIR
fi
ln -sfn "$(readlink -f "$i")" $SYSTEMD_TARGET_DIR/$(basename "$i")
fi
done
# Reload the systemd configuration
systemctl daemon-reload
# Start all units in the new configuration
for i in $profilePath/etc/systemd/system/*.service
do
systemctl start "$(basename "$i")"
done
# Delete obsolete users and groups
deleteObsoleteUsers
deleteObsoleteGroups
# Set new profile
setNixProfile

View File

@ -0,0 +1,22 @@
{stdenv, getopt}:
stdenv.mkDerivation {
name = "nixproc-sysvinit-tools";
buildCommand = ''
mkdir -p $out/bin
sed -e "s|/bin/bash|$SHELL|" \
-e "s|@getopt@|${getopt}/bin/getopt|" \
-e "s|@commonchecks@|${../commonchecks}|" \
-e "s|@sysvinitchecks@|${./sysvinitchecks}|" \
${./nixproc-sysvinit-switch.in} > $out/bin/nixproc-sysvinit-switch
chmod +x $out/bin/nixproc-sysvinit-switch
sed -e "s|/bin/bash|$SHELL|" \
-e "s|@getopt@|${getopt}/bin/getopt|" \
-e "s|@commonchecks@|${../commonchecks}|" \
-e "s|@sysvinitchecks@|${./sysvinitchecks}|" \
${./nixproc-sysvinit-runactivity.in} > $out/bin/nixproc-sysvinit-runactivity
chmod +x $out/bin/nixproc-sysvinit-runactivity
'';
}

View File

@ -0,0 +1,100 @@
#!/bin/bash
set -e
shopt -s nullglob
# Shows the usage of this command to the user
showUsage()
{
cat <<EOF
Usage: $0 [OPTION] ACTIVITY [PATH]
This command runs a deployment activity on all sysvinit scripts in a Nix
profile. The scripts are traversed in sequence order.
Options:
-p, --profile=NAME Name of the Nix profile that stores the sysvinit scripts
(defaults to: processes)
-r, --reverse Traverse the sysvinit scripts in the reverse order
--runlevel=LEVEL Specifies which runlevel to activate (defaults to the
runlevel of the system)
-h, --help Shows the usage of this command
Environment:
NIX_STATE_DIR Overrides the location of the Nix state directory
EOF
}
# Parse valid argument options
PARAMS=`@getopt@ -n $0 -o p:rh -l profile:,reverse,runlevel:,help -- "$@"`
if [ $? != 0 ]
then
showUsage
exit 1
fi
# Evaluate valid options
eval set -- "$PARAMS"
while [ "$1" != "--" ]
do
case "$1" in
-p|--profile)
profile="$2"
;;
-r|--reverse)
reverse=1
;;
--runlevel)
runlevel="$2"
;;
-h|--help)
showUsage
exit 0
;;
esac
shift
done
shift
activity="$1"
# Validate the given options
if [ "$activity" = "" ]
then
echo "No activity specified!" >&2
exit 1
fi
source @commonchecks@
checkNixStateDir
checkProfile
composeOldProfilePath
source @sysvinitchecks@
checkRunlevel
# Execute the activities
rcpath="$oldProfilePath/etc/rc.d/rc${runlevel}.d"
if [ "$reverse" = "1" ]
then
for i in $(ls $rcpath/S* | sort -r)
do
$i $activity
done
else
for i in $(ls $rcpath/S*)
do
$i $activity
done
fi

View File

@ -0,0 +1,181 @@
#!/bin/bash
set -e
shopt -s nullglob
# Shows the usage of this command to the user
showUsage()
{
cat <<EOF
Usage: $0 [OPTION] PATH
This command starts all sysvinit scripts in the provided Nix profile and
optionally deactivates all obsolete sysvinit scripts in the previous Nix
profile generation.
If the provided path is a file then it is considered a Nix expression that
produces a Nix profile. If the provided path is a directory, then it is
considered a pre-built Nix profile.
Options:
-p, --profile=NAME Name of the Nix profile that stores the sysvinit scripts
(defaults to: processes)
-o, --old-profile=PATH
Path to the previously deployed Nix profile (by default,
it gets auto detected)
--runlevel=LEVEL Specifies which runlevel to activate (defaults to the
runlevel of the system)
--state-dir Changes the directory in which the state of the
processes are stored
--runtime-dir Changes the directory in which the PID files are stored
--log-dir Changes the directory in which the log files are stored
--tmp-dir Changes the directory in which temp files are stored
--force-disable-user-change
Forces to not create users, groups or change user
permissions
--show-trace Shows a trace of the output
-h, --help Shows the usage of this command
Environment:
NIX_STATE_DIR Overrides the location of the Nix state directory
NIXPROC_STATE_DIR Changes the directory in which the state of the
processes is stored
NIXPROC_RUNTIME_DIR Changes the directory in which the PID files are stored
NIXPROC_LOG_DIR Changes the directory in which log files are stored
NIXPROC_TMP_DIR Changes the directory in which temp files are stored
NIXPROC_FORCE_DISABLE_USER_CHANGE
Forces to not create users, groups or change user
permissions
EOF
}
# Parse valid argument options
PARAMS=`@getopt@ -n $0 -o p:o:h -l profile:,old-profile:,state-dir:,runtime-dir:,log-dir:,tmp-dir:,force-disable-user-change,runlevel:,show-trace,help -- "$@"`
if [ $? != 0 ]
then
showUsage
exit 1
fi
# Evaluate valid options
eval set -- "$PARAMS"
while [ "$1" != "--" ]
do
case "$1" in
-p|--profile)
profile="$2"
;;
-o|--old-profile)
oldProfilePath="$2"
;;
--state-dir)
stateDirArg="--state-dir $2"
;;
--runtime-dir)
runtimeDirArg="--runtime-dir $2"
;;
--log-dir)
logDirArg="--log-dir $2"
;;
--tmp-dir)
tmpDirArg="--tmp-dir $2"
;;
--force-disable-user-change)
forceDisableUserChangeArg="--force-disable-user-change"
;;
--runlevel)
runlevel="$2"
;;
--show-trace)
showTraceArg="--show-trace"
;;
-h|--help)
showUsage
exit 0
;;
esac
shift
done
shift
path="$1"
# Validate the given options
source @commonchecks@
checkNixStateDir
checkProfile
composeOldProfilePath
source @sysvinitchecks@
checkRunlevel
# Build the environment with sysvinit scripts
buildProfile sysvinit
rcnew="$profilePath/etc/rc.d/rc${runlevel}.d"
rcold="$oldProfilePath/etc/rc.d/rc${runlevel}.d"
# Determine paths of old scripts
oldscripts=()
if [ -d "$rcold" ]
then
echo "Using previous Nix profile: $rcold" >&2
for i in $(ls $rcold/S* | sort -r)
do
currentPath=$(readlink -f $i)
oldscripts+=($currentPath)
done
fi
# Determine paths of new scripts
newscripts=()
for i in $(ls $rcnew/S*)
do
currentPath=$(readlink -f $i)
newscripts+=($currentPath)
done
# Create new groups and users
createNewGroups
createNewUsers
# Stop obsolete scripts
for i in ${oldscripts[@]}
do
if ! containsElement "$i" "${newscripts[@]}"
then
$i stop
fi
done
# Start new scripts
for i in ${newscripts[@]}
do
if ! containsElement "$i" "${oldscripts[@]}"
then
$i start
fi
done
# Delete obsolete users and groups
deleteObsoleteUsers
deleteObsoleteGroups
# Set new profile
setNixProfile

View File

@ -0,0 +1,9 @@
#!/bin/bash -e
checkRunlevel()
{
if [ "$runlevel" = "" ]
then
runlevel=$(runlevel | cut -d ' ' -f2)
fi
}

14
webapp/Makefile Normal file
View File

@ -0,0 +1,14 @@
CC = cc
all:
$(CC) $(CFLAGS) -c daemonize.c
$(CC) $(CFLAGS) -c service.c
$(CC) $(CFLAGS) $(LDFLAGS) daemonize.o service.o main.c -lmicrohttpd -o webapp
install: all
install -d -m755 $(PREFIX)/bin
install -m755 webapp $(PREFIX)/bin
clean:
rm -f *.o
rm -f webapp

18
webapp/README.md Normal file
View File

@ -0,0 +1,18 @@
Test web application
====================
This is a very simple test web application that can run in foreground mode and
daemon mode. Its only purpose is to return a very simple static HTML page.
The most interesting part of this example is probably the daemonize
infrastructure (`daemonize.h`, `daemonize.c`) -- I have been trying to closely
follow systemd's recommendations for implementing traditional SysV daemons
(more info:`man 7 daemon`) sticking myself to POSIX standards as much as
possible.
To keep the code as clear as possible, I have encapsulated each recommended
step into a function abstraction, and every failure yields a distinct error
code so that we can easily trace the origins of the error.
The daemonize infrastructure is very generic -- you only need to provide a
pointer to a function that initializes the daemon's state and a pointer to
a function that runs the main loop.

259
webapp/daemonize.c Normal file
View File

@ -0,0 +1,259 @@
#include "daemonize.h"
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#define NULL_DEV_FILE "/dev/null"
#define TRUE 1
#define FALSE 0
#define BUFFER_SIZE 10
static int close_non_standard_file_descriptors(void)
{
unsigned int i;
struct rlimit rlim;
int num_of_fds = getrlimit(RLIMIT_NOFILE, &rlim);
if(num_of_fds == -1)
return FALSE;
for(i = 3; i < num_of_fds; i++)
close(i);
return TRUE;
}
static int reset_signal_handlers_to_default(void)
{
#if defined _NSIG
unsigned int i;
for(i = 1; i < _NSIG; i++)
{
if(i != SIGKILL && i != SIGSTOP)
signal(i, SIG_DFL);
}
#endif
return TRUE;
}
static int clear_signal_mask(void)
{
sigset_t set;
return((sigemptyset(&set) == 0)
&& (sigprocmask(SIG_SETMASK, &set, NULL) == 0));
}
static int create_pid_file(const char *pid_file)
{
pid_t my_pid = getpid();
char my_pid_str[10];
int fd;
sprintf(my_pid_str, "%d", my_pid);
if((fd = open(pid_file, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR)) == -1)
return FALSE;
if(write(fd, my_pid_str, strlen(my_pid_str)) == -1)
return FALSE;
close(fd);
return TRUE;
}
static int attach_standard_file_descriptors_to_null(void)
{
int null_fd_read, null_fd_write;
return(((null_fd_read = open(NULL_DEV_FILE, O_RDONLY)) != -1)
&& (dup2(null_fd_read, STDIN_FILENO) != -1)
&& ((null_fd_write = open(NULL_DEV_FILE, O_WRONLY)) != -1)
&& (dup2(null_fd_write, STDOUT_FILENO) != -1)
&& (dup2(null_fd_write, STDERR_FILENO) != -1));
}
static void notify_parent_process(int writefd, DaemonStatus message)
{
char byte = (char)message;
while(write(writefd, &byte, 1) == 0);
close(writefd);
}
static pid_t fork_daemon_process(int writefd, const char *pid_file, void *data, int (*initialize_daemon) (void *data), int (*run_main_loop) (void *data))
{
pid_t pid = fork();
if(pid == 0)
{
if(!attach_standard_file_descriptors_to_null())
{
notify_parent_process(writefd, STATUS_CANNOT_ATTACH_STD_FDS_TO_NULL);
exit(STATUS_CANNOT_ATTACH_STD_FDS_TO_NULL);
}
umask(0);
if(chdir("/") == -1)
{
notify_parent_process(writefd, STATUS_CANNOT_CHDIR);
exit(STATUS_CANNOT_CHDIR);
}
if(!create_pid_file(pid_file))
{
notify_parent_process(writefd, STATUS_CANNOT_CREATE_PID_FILE);
exit(STATUS_CANNOT_CREATE_PID_FILE);
}
if(!initialize_daemon(data))
{
notify_parent_process(writefd, STATUS_CANNOT_INIT_DAEMON);
unlink(pid_file);
exit(STATUS_CANNOT_INIT_DAEMON);
}
notify_parent_process(writefd, STATUS_INIT_SUCCESS);
int exit_status = run_main_loop(data);
if(unlink(pid_file) == 0)
exit(STATUS_CANNOT_UNLINK_PID_FILE);
exit(exit_status);
}
return pid;
}
static pid_t fork_helper_process(int pipefd[2], const char *pid_file, void *data, int (*initialize_daemon) (void *data), int (*run_main_loop) (void *data))
{
pid_t pid = fork();
if(pid == 0)
{
close(pipefd[0]); /* Close unneeded read-end */
if(setsid() == -1)
{
notify_parent_process(pipefd[1], STATUS_CANNOT_SET_SID);
exit(STATUS_CANNOT_SET_SID);
}
/* Fork again, so that the terminal can not be acquired again */
if(fork_daemon_process(pipefd[1], pid_file, data, initialize_daemon, run_main_loop) == -1)
{
notify_parent_process(pipefd[1], STATUS_CANNOT_FORK_DAEMON_PROCESS);
exit(STATUS_CANNOT_FORK_DAEMON_PROCESS);
}
exit(0); /* Exit the helper process, so that the daemon process gets adopted by PID 1 */
}
return pid;
}
static DaemonStatus wait_for_notification_message(int readfd)
{
char buf[BUFFER_SIZE];
ssize_t bytes_read = read(readfd, buf, 1);
if(bytes_read == -1)
return STATUS_CANNOT_READ_FROM_PIPE;
else if(bytes_read == 0)
return STATUS_UNKNOWN_DAEMON_ERROR;
else
return buf[0];
}
DaemonStatus daemonize(const char *pid_file, void *data, int (*initialize_daemon) (void *data), int (*run_main_loop) (void *data))
{
int pipefd[2];
if(!close_non_standard_file_descriptors())
return STATUS_CANNOT_CLOSE_NON_STD_FDS;
if(!reset_signal_handlers_to_default())
return STATUS_CANNOT_RESET_SIGNAL_HANDLERS;
if(!clear_signal_mask())
return STATUS_CANNOT_CLEAR_SIGNAL_MASK;
if(pipe(pipefd) == -1)
return STATUS_CANNOT_CREATE_PIPE;
else
{
if(fork_helper_process(pipefd, pid_file, data, initialize_daemon, run_main_loop) == -1)
return STATUS_CANNOT_FORK_HELPER_PROCESS;
else
{
DaemonStatus exit_status;
close(pipefd[1]); /* Close unneeded write end */
exit_status = wait_for_notification_message(pipefd[0]);
close(pipefd[0]);
return exit_status;
}
}
}
void print_daemon_status(DaemonStatus status, FILE *file)
{
switch(status)
{
case STATUS_INIT_SUCCESS:
fprintf(file, "Daemon initialized successfully!\n");
break;
case STATUS_CANNOT_ATTACH_STD_FDS_TO_NULL:
fprintf(file, "Cannot attach standard file descriptors to " NULL_DEV_FILE " !\n");
break;
case STATUS_CANNOT_CHDIR:
fprintf(file, "Cannot change current working directory to /\n");
break;
case STATUS_CANNOT_CREATE_PID_FILE:
fprintf(file, "Cannot create PID file!\n");
break;
case STATUS_CANNOT_INIT_DAEMON:
fprintf(file, "Cannot initialize the daemon!\n");
break;
case STATUS_CANNOT_UNLINK_PID_FILE:
fprintf(file, "Cannot unlink PID file!\n");
break;
case STATUS_CANNOT_CLOSE_NON_STD_FDS:
fprintf(file, "Cannot close the non standard file descriptors!\n");
break;
case STATUS_CANNOT_RESET_SIGNAL_HANDLERS:
fprintf(file, "Cannot reset signal handlers!\n");
break;
case STATUS_CANNOT_CLEAR_SIGNAL_MASK:
fprintf(file, "Cannot clear signal mask!\n");
break;
case STATUS_CANNOT_CREATE_PIPE:
fprintf(file, "Cannot create pipe!\n");
break;
case STATUS_CANNOT_FORK_HELPER_PROCESS:
fprintf(file, "Cannot fork helper process!\n");
break;
case STATUS_CANNOT_READ_FROM_PIPE:
fprintf(file, "Cannot read from pipe!\n");
break;
case STATUS_CANNOT_SET_SID:
fprintf(file, "Cannot set session ID!\n");
break;
case STATUS_CANNOT_FORK_DAEMON_PROCESS:
fprintf(file, "Cannot fork daemon process!\n");
break;
case STATUS_UNKNOWN_DAEMON_ERROR:
fprintf(file, "Unknown daemon error!\n");
break;
}
}

29
webapp/daemonize.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef __DAEMONIZE_H
#define __DAEMONIZE_H
#include <stdio.h>
typedef enum
{
STATUS_INIT_SUCCESS = 0x0,
STATUS_CANNOT_ATTACH_STD_FDS_TO_NULL = 0x1,
STATUS_CANNOT_CHDIR = 0x2,
STATUS_CANNOT_CREATE_PID_FILE = 0x3,
STATUS_CANNOT_INIT_DAEMON = 0x4,
STATUS_CANNOT_UNLINK_PID_FILE = 0x5,
STATUS_CANNOT_CLOSE_NON_STD_FDS = 0x6,
STATUS_CANNOT_RESET_SIGNAL_HANDLERS = 0x7,
STATUS_CANNOT_CLEAR_SIGNAL_MASK = 0x8,
STATUS_CANNOT_CREATE_PIPE = 0x9,
STATUS_CANNOT_FORK_HELPER_PROCESS = 0xa,
STATUS_CANNOT_READ_FROM_PIPE = 0xb,
STATUS_CANNOT_SET_SID = 0xc,
STATUS_CANNOT_FORK_DAEMON_PROCESS = 0xd,
STATUS_UNKNOWN_DAEMON_ERROR = 0xe
}
DaemonStatus;
DaemonStatus daemonize(const char *pid_file, void *data, int (*initialize_daemon) (void *data), int (*run_main_loop) (void *data));
void print_daemon_status(DaemonStatus status, FILE *file);
#endif

11
webapp/default.nix Normal file
View File

@ -0,0 +1,11 @@
with import <nixpkgs> {};
stdenv.mkDerivation {
name = "webapp";
src = ./.;
buildInputs = [ libmicrohttpd ];
CFLAGS = "-I${libmicrohttpd.dev}/include";
LDFLAGS = "-L${libmicrohttpd}/lib";
makeFlags = "PREFIX=$(out)";
dontStrip = true;
}

44
webapp/main.c Normal file
View File

@ -0,0 +1,44 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "service.h"
#define TRUE 1
#define FALSE 0
int main(int argc, char *argv[])
{
unsigned int i;
int run_as_daemon = FALSE;
int port;
char *port_value = getenv("PORT");
if(port_value == NULL)
{
fprintf(stderr, "We need a PORT environment variable that specifies to which port the HTTP service should bind to!\n");
return 1;
}
port = atoi(port_value);
for(i = 1; i < argc; i++)
{
if(strcmp(argv[i], "-D") == 0)
run_as_daemon = TRUE;
}
if(run_as_daemon)
{
char *pid_file = getenv("PID_FILE");
if(pid_file == NULL)
{
fprintf(stderr, "We need the PID_FILE environment variable that specifies the path to a file storing the PID of the daemon process!\n");
return 1;
}
return run_daemon(port, pid_file);
}
else
return run_foreground_process(port);
}

158
webapp/service.c Normal file
View File

@ -0,0 +1,158 @@
#include "service.h"
#include <microhttpd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <signal.h>
#define TRUE 1
#define FALSE 0
#define PAGE_TEMPLATE "<!DOCTYPE html>\n"\
"<html>\n"\
" <head>\n"\
" <title>Simple test webapp</title>\n"\
" </head>\n"\
" <body>\n"\
" Simple test webapp listening on port: %d\n"\
" </body>\n"\
"</html>\n"
static int ahc_echo(void *cls, struct MHD_Connection *connection, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **ptr)
{
static int dummy;
const char *page = cls;
struct MHD_Response *response;
int ret;
if(strcmp(method, "GET") != 0)
return MHD_NO; /* unexpected method */
if(&dummy != *ptr)
{
/* The first time only the headers are valid,
do not respond in the first round... */
*ptr = &dummy;
return MHD_YES;
}
if (*upload_data_size != 0)
return MHD_NO; /* upload data in a GET!? */
*ptr = NULL; /* clear context pointer */
response = MHD_create_response_from_buffer(strlen(page), (void*)page, MHD_RESPMEM_PERSISTENT);
ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
MHD_destroy_response(response);
return ret;
}
volatile int terminated = FALSE;
static void handle_shutdown(int signum)
{
terminated = TRUE;
}
typedef struct
{
char *page;
struct MHD_Daemon *daemon;
}
DaemonWrapper;
static DaemonWrapper *create_daemon_wrapper(int port)
{
DaemonWrapper *wrapper = (DaemonWrapper*)malloc(sizeof(DaemonWrapper));
wrapper->page = (char*)malloc(sizeof(PAGE_TEMPLATE) + 10);
sprintf(wrapper->page, PAGE_TEMPLATE, port);
wrapper->daemon = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, port, NULL, NULL, &ahc_echo, wrapper->page, MHD_OPTION_END);
if(wrapper->daemon == NULL)
{
free(wrapper->page);
free(wrapper);
return NULL;
}
else
{
signal(SIGINT, handle_shutdown);
signal(SIGTERM, handle_shutdown);
return wrapper;
}
}
static void delete_daemon_wrapper(DaemonWrapper *wrapper)
{
if(wrapper != NULL)
{
MHD_stop_daemon(wrapper->daemon);
free(wrapper->page);
free(wrapper);
}
}
static int run_main_loop(void)
{
/* Loop until termination request was received */
while(!terminated)
sleep(1);
return 0;
}
int run_foreground_process(int port)
{
DaemonWrapper *wrapper = create_daemon_wrapper(port);
if(wrapper == NULL)
{
fprintf(stderr, "Cannot start HTTP service!\n");
return 1;
}
else
{
int exit_status = run_main_loop();
delete_daemon_wrapper(wrapper);
return exit_status;
}
}
typedef struct
{
int port;
DaemonWrapper *wrapper;
}
DaemonConfig;
static int init_http_service(void *data)
{
DaemonConfig *config = (DaemonConfig*)data;
config->wrapper = create_daemon_wrapper(config->port);
return (config->wrapper != NULL);
}
static int run_http_service(void *data)
{
return run_main_loop();
}
DaemonStatus run_daemon(int port, const char *pid_file)
{
DaemonConfig config;
config.port = port;
DaemonStatus exit_status = daemonize(pid_file, &config, init_http_service, run_http_service);
if(exit_status != STATUS_INIT_SUCCESS)
print_daemon_status(exit_status, stderr);
return exit_status;
}

9
webapp/service.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef __SERVICE_H
#define __SERVICE_H
#include "daemonize.h"
int run_foreground_process(int port);
DaemonStatus run_daemon(int port, const char *pid_file);
#endif