Browse Source

rails: Support background workers and other Rails services/workers

The new `services' option is used to request additional processes be
run in the background with the same environment as the main Rails
process.
pjones/monitoring
Peter J. Jones 10 months ago
parent
commit
4964d95974
Signed by: Peter Jones <pjones@devalot.com> GPG Key ID: 9DAFAA8D01941E49

+ 1
- 1
lib/keys.nix View File

@@ -1,5 +1,5 @@
# Functions for working with NixOps keys.
{ lib }:
{ lib, ... }:

with lib;


+ 27
- 0
lib/shell.nix View File

@@ -0,0 +1,27 @@
# Functions for generating and working with shell scripts:
{ lib, pkgs, ... }:


with lib;

let
funcs = rec {

# Generate a shell script that exports the given variables.
#
# Type:
#
# string -> attrset -> derivation
#
# Arguments:
#
# fileName: The name of the file in the nix store to create.
# attrs: The variables to include in the generated script.
#
attrsToShellExports = fileName: attrs:
let export = name: value: "export ${name}=${escapeShellArg value}";
lines = mapAttrsToList export attrs;
in pkgs.writeText fileName (concatStringsSep "\n" lines);
};

in funcs

+ 2
- 1
modules/default.nix View File

@@ -5,9 +5,10 @@ with lib;
let
libFiles = [
../lib/keys.nix
../lib/shell.nix
];

loadLib = path: import path { inherit lib; };
loadLib = path: import path { inherit lib pkgs; };
libs = foldr (a: b: recursiveUpdate (loadLib a) b) {} libFiles;

in

+ 48
- 21
modules/services/web/rails/default.nix View File

@@ -12,6 +12,17 @@ let
scripts = import ./scripts.nix { inherit lib pkgs; };
options = import ./options.nix { inherit config lib pkgs; };

##############################################################################
# The main Rails service:
mainService = app: {
name = "main";
isMain = true;

script = ''
puma -e ${app.railsEnv} -p ${toString app.port}
'';
};

##############################################################################
# Is PostgreSQL local?
localpg = config.phoebe.services.postgresql.enable;
@@ -21,6 +32,18 @@ let
# propagatedBuildInputs won't always be set.
appPath = app: [ app.package.rubyEnv ] ++ app.package.propagatedBuildInputs;

##############################################################################
# All of the environment variables that a Rails app needs:
appEnv = app: {
HOME = "${app.home}/home";
RAILS_ENV = app.railsEnv;
DATABASE_HOST = app.database.host;
DATABASE_PORT = toString app.database.port;
DATABASE_NAME = app.database.name;
DATABASE_USER = app.database.user;
DATABASE_PASSWORD_FILE = "${app.home}/state/database.password";
} // app.environment;

##############################################################################
# Collect all apps into a single set using the given function:
collectApps = f: foldr (a: b: recursiveUpdate b (f a)) {} (attrValues cfg.apps);
@@ -55,24 +78,15 @@ let

##############################################################################
# Generate a systemd service for a Ruby on Rails application:
appService = app: {
"rails-${app.name}" = {
description = "${app.name} (Ruby on Rails)";
appService = app: service: {
"rails-${app.name}-${service.name}" = {
description = "${app.name} (Ruby on Rails) ${service.name}";
path = appPath app;

environment = {
HOME = "${app.home}/home";
RAILS_ENV = app.railsEnv;
DATABASE_HOST = app.database.host;
DATABASE_PORT = toString app.database.port;
DATABASE_NAME = app.database.name;
DATABASE_USER = app.database.user;
DATABASE_PASSWORD_FILE = "${app.home}/state/database.password";
} // app.environment;

environment = appEnv app;
wantedBy = [ "multi-user.target" ];

wants =
optional (!service.isMain) "rails-${app.name}-main" ++
plib.keyService app.database.passwordFile ++
plib.keyService app.sourcedFile;

@@ -80,10 +94,11 @@ let
[ "network.target" ] ++
optional localpg "postgresql.service" ++
optional localpg "pg-accounts.service" ++
optional (!service.isMain) "rails-${app.name}-main" ++
plib.keyService app.database.passwordFile ++
plib.keyService app.sourcedFile;

preStart = ''
preStart = optionalString service.isMain ''
# Prepare the config directory:
rm -rf ${app.home}/config
mkdir -p ${app.home}/{config,log,tmp,db,state}
@@ -93,8 +108,12 @@ let
cp ${./database.yml} ${app.home}/config/database.yml
cp ${app.database.passwordFile} ${app.home}/state/database.password

# Additional set up for the home directory:
mkdir -p ${app.home}/home
ln -nfs ${app.package}/share/${app.name} ${app.home}/home/${app.name}
ln -nfs ${plib.attrsToShellExports "rails-${app.name}-env" (appEnv app)} ${app.home}/home/.env
cp ${./profile.sh} ${app.home}/home/.profile
chmod 0700 ${app.home}/home/.profile

# Copy the sourcedFile if necessary:
${optionalString (app.sourcedFile != null) ''
@@ -106,9 +125,9 @@ let
chmod go+rx $(dirname "${app.home}")
chmod u+w ${app.home}/db/schema.rb

'' + optionalString app.database.migrate ''
# Migrate the database (use sudo so environment variables go through):
${pkgs.sudo}/bin/sudo -u rails-${app.name} -EH \
'' + optionalString (service.isMain && app.database.migrate) ''
# Migrate the database:
${pkgs.sudo}/bin/sudo --user=rails-${app.name} --login \
${scripts}/bin/db-migrate.sh \
-r ${app.package}/share/${app.name} \
-s ${app.home}/state
@@ -116,7 +135,7 @@ let

script = ''
${optionalString (app.sourcedFile != null) ". ${app.home}/state/sourcedFile.sh"}
${app.package.rubyEnv}/bin/puma -e ${app.railsEnv} -p ${toString app.port}
${service.script}
'';

serviceConfig = {
@@ -132,6 +151,13 @@ let
};
};

##############################################################################
# Collect all services for a given application and turn them into
# systemd services.
appServices = app:
foldr (service: set: recursiveUpdate set (appService app service)) {}
( [(mainService app)] ++ attrValues app.services );

##############################################################################
# Generate a user account for a Ruby on Rails application:
appUser = app: {
@@ -173,7 +199,8 @@ in
# Each application gets a user account:
users = collectApps appUser;

# Each application gets a systemd service to keep it running.
systemd.services = collectApps appService;
# Each application gets one or more systemd services to keep it
# running.
systemd.services = collectApps appServices;
};
}

+ 43
- 0
modules/services/web/rails/options.nix View File

@@ -52,6 +52,35 @@ let
};
};

##############################################################################
# Service configuration:
service = { name, ... }: {
options = {
name = mkOption {
type = types.str;
example = "sidekiq";
description = "The name of the additional service to run.";
};

script = mkOption {
type = types.lines;
example = "sidekiq -c 5 -v -q default";
description = "Shell commands executed as the service's main process.";
};

isMain = mkOption {
internal = true;
type = types.bool;
default = false;
description = "Is this the main Rails process?";
};
};

config = {
name = mkDefault name;
};
};

##############################################################################
# Application configuration:
application = { name, ... }: {
@@ -88,6 +117,20 @@ let
description = "Database configuration.";
};

services = mkOption {
type = types.attrsOf (types.submodule service);
default = { };
description = ''
Additional services to run for this Rails application. For
example, if you need to have background queue processing
scripts running this is where you'd want to do that.

All of the listed services are run via systemd and are
executed in the same environment as the main Rails
application itself.
'';
};

railsEnv = mkOption {
type = types.str;
default = "production";

+ 9
- 0
modules/services/web/rails/profile.sh View File

@@ -0,0 +1,9 @@
# Rails user shell profile.

if [ -e "$HOME/.env" ]; then
. "$HOME/.env"
fi

if [ -e "$HOME/../state/sourcedFile.sh" ]; then
. "$HOME/../state/sourcedFile.sh"
fi

Loading…
Cancel
Save