mirror of
https://github.com/vale981/phoebe
synced 2025-03-04 09:21:40 -05:00
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.
This commit is contained in:
parent
193b82189e
commit
4964d95974
6 changed files with 130 additions and 23 deletions
|
@ -1,5 +1,5 @@
|
|||
# Functions for working with NixOps keys.
|
||||
{ lib }:
|
||||
{ lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
|
|
27
lib/shell.nix
Normal file
27
lib/shell.nix
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
modules/services/web/rails/profile.sh
Normal file
9
modules/services/web/rails/profile.sh
Normal 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…
Add table
Reference in a new issue