mirror of
https://github.com/vale981/phoebe
synced 2025-03-05 09:51:37 -05:00
rails: New systemd target for each Rails application
* Each now has a systemd target so you can start and stop all services together. * New `enable' option so you can prevent services from starting (e.g., on development machines)
This commit is contained in:
parent
eaa5c89f6b
commit
e742614c30
4 changed files with 189 additions and 139 deletions
|
@ -8,42 +8,11 @@ let
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# Save some typing.
|
# Save some typing.
|
||||||
cfg = config.phoebe.services.rails;
|
cfg = config.phoebe.services.rails;
|
||||||
plib = config.phoebe.lib;
|
|
||||||
scripts = import ./scripts.nix { inherit lib pkgs; };
|
|
||||||
options = import ./options.nix { inherit config lib pkgs; };
|
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# The main Rails service:
|
options = import ./options.nix { inherit config lib pkgs; };
|
||||||
mainService = app: {
|
appSystemd = import ./systemd.nix { inherit config pkgs lib; };
|
||||||
name = "main";
|
funcs = import ./functions.nix { inherit config; };
|
||||||
schedule = null;
|
|
||||||
isMain = true;
|
|
||||||
|
|
||||||
script = ''
|
|
||||||
puma -e ${app.railsEnv} -p ${toString app.port}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
##############################################################################
|
|
||||||
# Is PostgreSQL local?
|
|
||||||
localpg = config.phoebe.services.postgresql.enable;
|
|
||||||
|
|
||||||
##############################################################################
|
|
||||||
# Packages to put in the application's PATH. FIXME:
|
|
||||||
# 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:
|
# Collect all apps into a single set using the given function:
|
||||||
|
@ -92,106 +61,6 @@ let
|
||||||
}
|
}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
##############################################################################
|
|
||||||
# Generate a systemd service for a Ruby on Rails application:
|
|
||||||
appService = app: service: {
|
|
||||||
"rails-${app.name}-${service.name}" = {
|
|
||||||
description = "${app.name} (Ruby on Rails) ${service.name}";
|
|
||||||
path = appPath app;
|
|
||||||
environment = appEnv app;
|
|
||||||
|
|
||||||
# Only start this service if it isn't scheduled by a timer.
|
|
||||||
wantedBy = optional (service.schedule == null) "multi-user.target";
|
|
||||||
|
|
||||||
wants =
|
|
||||||
plib.keyService app.database.passwordFile ++
|
|
||||||
plib.keyService app.sourcedFile;
|
|
||||||
|
|
||||||
after =
|
|
||||||
[ "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 = optionalString service.isMain ''
|
|
||||||
# Prepare the config directory:
|
|
||||||
rm -rf ${app.home}/config
|
|
||||||
mkdir -p ${app.home}/{config,log,tmp,db,state}
|
|
||||||
|
|
||||||
cp -rf ${app.package}/share/${app.name}/config.dist/* ${app.home}/config/
|
|
||||||
cp ${app.package}/share/${app.name}/db/schema.rb.dist ${app.home}/db/schema.rb
|
|
||||||
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) ''
|
|
||||||
cp ${app.sourcedFile} ${app.home}/state/sourcedFile.sh
|
|
||||||
''}
|
|
||||||
|
|
||||||
# Fix permissions:
|
|
||||||
chown -R rails-${app.name}:rails-${app.name} ${app.home}
|
|
||||||
chmod go+rx $(dirname "${app.home}")
|
|
||||||
chmod u+w ${app.home}/db/schema.rb
|
|
||||||
|
|
||||||
'' + 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
|
|
||||||
'';
|
|
||||||
|
|
||||||
script = ''
|
|
||||||
${optionalString (app.sourcedFile != null) ". ${app.home}/state/sourcedFile.sh"}
|
|
||||||
${service.script}
|
|
||||||
'';
|
|
||||||
|
|
||||||
serviceConfig = {
|
|
||||||
WorkingDirectory = "${app.package}/share/${app.name}";
|
|
||||||
Restart = "on-failure";
|
|
||||||
TimeoutSec = "infinity"; # FIXME: what's a reasonable amount of time?
|
|
||||||
Type = "simple";
|
|
||||||
PermissionsStartOnly = true;
|
|
||||||
User = "rails-${app.name}";
|
|
||||||
Group = "rails-${app.name}";
|
|
||||||
UMask = "0077";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
##############################################################################
|
|
||||||
# Schedule some services with a systemd timer:
|
|
||||||
appTimer = app: service: optionalAttrs (service.schedule != null) {
|
|
||||||
"rails-${app.name}-${service.name}" = {
|
|
||||||
description = "${app.name} (Ruby on Rails) ${service.name}";
|
|
||||||
wantedBy = [ "timers.target" ];
|
|
||||||
timerConfig.OnCalendar = service.schedule;
|
|
||||||
timerConfig.Unit = "rails-${app.name}-${service.name}.service";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
##############################################################################
|
|
||||||
# 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 );
|
|
||||||
|
|
||||||
##############################################################################
|
|
||||||
# Collect all services and turn them into systemd timers:
|
|
||||||
appTimers = app:
|
|
||||||
foldr (service: set: recursiveUpdate set (appTimer app service)) {}
|
|
||||||
(attrValues app.services);
|
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# Generate a user account for a Ruby on Rails application:
|
# Generate a user account for a Ruby on Rails application:
|
||||||
appUser = app: {
|
appUser = app: {
|
||||||
|
@ -202,7 +71,7 @@ let
|
||||||
group = "rails-${app.name}";
|
group = "rails-${app.name}";
|
||||||
shell = "${pkgs.bash}/bin/bash";
|
shell = "${pkgs.bash}/bin/bash";
|
||||||
extraGroups = [ config.services.nginx.group ];
|
extraGroups = [ config.services.nginx.group ];
|
||||||
packages = appPath app;
|
packages = funcs.appPath app;
|
||||||
};
|
};
|
||||||
groups."rails-${app.name}" = {};
|
groups."rails-${app.name}" = {};
|
||||||
};
|
};
|
||||||
|
@ -234,9 +103,8 @@ in
|
||||||
users = collectApps appUser;
|
users = collectApps appUser;
|
||||||
|
|
||||||
# Each application gets one or more systemd services to keep it
|
# Each application gets one or more systemd services to keep it
|
||||||
# running.
|
# running. There's also a systemd target and some timers.
|
||||||
systemd.services = collectApps appServices;
|
systemd = collectApps appSystemd;
|
||||||
systemd.timers = collectApps appTimers;
|
|
||||||
|
|
||||||
# Rotate all of the log files:
|
# Rotate all of the log files:
|
||||||
services.logrotate = {
|
services.logrotate = {
|
||||||
|
|
|
@ -1,7 +1,34 @@
|
||||||
|
{ config }:
|
||||||
|
|
||||||
rec {
|
rec {
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
# The default base directory for Rails applications:
|
# The default base directory for Rails applications:
|
||||||
base = "/var/lib/rails";
|
base = "/var/lib/rails";
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
# Where a Rails application lives:
|
# Where a Rails application lives:
|
||||||
home = name: "${base}/${name}";
|
home = name: "${base}/${name}";
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# Is PostgreSQL local?
|
||||||
|
localpg = config.phoebe.services.postgresql.enable;
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# Packages to put in the application's PATH. FIXME:
|
||||||
|
# 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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ with lib;
|
||||||
|
|
||||||
let
|
let
|
||||||
##############################################################################
|
##############################################################################
|
||||||
functions = import ./functions.nix;
|
functions = import ./functions.nix { inherit config; };
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# Database configuration:
|
# Database configuration:
|
||||||
|
@ -102,6 +102,20 @@ let
|
||||||
description = "The name of the Ruby on Rails application.";
|
description = "The name of the Ruby on Rails application.";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
example = false;
|
||||||
|
description = ''
|
||||||
|
Whether to enable this application by default.
|
||||||
|
|
||||||
|
Setting this value to false will prevent any of the systemd
|
||||||
|
services from starting. This is useful for creating
|
||||||
|
development environments where everything is set up but
|
||||||
|
nothing is running.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
home = mkOption {
|
home = mkOption {
|
||||||
type = types.path;
|
type = types.path;
|
||||||
description = "The directory where the application is deployed to.";
|
description = "The directory where the application is deployed to.";
|
||||||
|
|
141
modules/services/web/rails/systemd.nix
Normal file
141
modules/services/web/rails/systemd.nix
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
{ config
|
||||||
|
, pkgs
|
||||||
|
, lib
|
||||||
|
}:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# Helpful functions.
|
||||||
|
plib = config.phoebe.lib;
|
||||||
|
funcs = import ./functions.nix { inherit config; };
|
||||||
|
scripts = import ./scripts.nix { inherit lib pkgs; };
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# The main Rails service:
|
||||||
|
mainService = app: {
|
||||||
|
name = "main";
|
||||||
|
schedule = null;
|
||||||
|
isMain = true;
|
||||||
|
|
||||||
|
script = ''
|
||||||
|
puma -e ${app.railsEnv} -p ${toString app.port}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# Generate a systemd service for a Ruby on Rails application:
|
||||||
|
appService = app: service: {
|
||||||
|
"rails-${app.name}-${service.name}" = {
|
||||||
|
description = "${app.name} (Ruby on Rails) ${service.name}";
|
||||||
|
path = funcs.appPath app;
|
||||||
|
environment = funcs.appEnv app;
|
||||||
|
|
||||||
|
# Only start this service if it isn't scheduled by a timer.
|
||||||
|
partOf = optional (service.schedule == null) "rails-${app.name}.target";
|
||||||
|
wantedBy = optional (service.schedule == null) "rails-${app.name}.target";
|
||||||
|
|
||||||
|
wants =
|
||||||
|
plib.keyService app.database.passwordFile ++
|
||||||
|
plib.keyService app.sourcedFile;
|
||||||
|
|
||||||
|
after =
|
||||||
|
[ "network.target" ] ++
|
||||||
|
optional funcs.localpg "postgresql.service" ++
|
||||||
|
optional funcs.localpg "pg-accounts.service" ++
|
||||||
|
optional (!service.isMain) "rails-${app.name}-main" ++
|
||||||
|
plib.keyService app.database.passwordFile ++
|
||||||
|
plib.keyService app.sourcedFile;
|
||||||
|
|
||||||
|
preStart = optionalString service.isMain ''
|
||||||
|
# Prepare the config directory:
|
||||||
|
rm -rf ${app.home}/config
|
||||||
|
mkdir -p ${app.home}/{config,log,tmp,db,state}
|
||||||
|
|
||||||
|
cp -rf ${app.package}/share/${app.name}/config.dist/* ${app.home}/config/
|
||||||
|
cp ${app.package}/share/${app.name}/db/schema.rb.dist ${app.home}/db/schema.rb
|
||||||
|
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" (funcs.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) ''
|
||||||
|
cp ${app.sourcedFile} ${app.home}/state/sourcedFile.sh
|
||||||
|
''}
|
||||||
|
|
||||||
|
# Fix permissions:
|
||||||
|
chown -R rails-${app.name}:rails-${app.name} ${app.home}
|
||||||
|
chmod go+rx $(dirname "${app.home}")
|
||||||
|
chmod u+w ${app.home}/db/schema.rb
|
||||||
|
|
||||||
|
'' + 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
|
||||||
|
'';
|
||||||
|
|
||||||
|
script = ''
|
||||||
|
${optionalString (app.sourcedFile != null) ". ${app.home}/state/sourcedFile.sh"}
|
||||||
|
${service.script}
|
||||||
|
'';
|
||||||
|
|
||||||
|
serviceConfig = {
|
||||||
|
WorkingDirectory = "${app.package}/share/${app.name}";
|
||||||
|
Restart = "on-failure";
|
||||||
|
TimeoutSec = "infinity"; # FIXME: what's a reasonable amount of time?
|
||||||
|
Type = "simple";
|
||||||
|
PermissionsStartOnly = true;
|
||||||
|
User = "rails-${app.name}";
|
||||||
|
Group = "rails-${app.name}";
|
||||||
|
UMask = "0077";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# Schedule some services with a systemd timer:
|
||||||
|
appTimer = app: service: optionalAttrs (service.schedule != null) {
|
||||||
|
"rails-${app.name}-${service.name}" = {
|
||||||
|
description = "${app.name} (Ruby on Rails) ${service.name}";
|
||||||
|
wantedBy = optional app.enable "timers.target";
|
||||||
|
timerConfig.OnCalendar = service.schedule;
|
||||||
|
timerConfig.Unit = "rails-${app.name}-${service.name}.service";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# 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 );
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# Collect all services and turn them into systemd timers:
|
||||||
|
appTimers = app:
|
||||||
|
foldr (service: set: recursiveUpdate set (appTimer app service)) {}
|
||||||
|
(attrValues app.services);
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# All systemd settings for an application:
|
||||||
|
appSystemd = app: {
|
||||||
|
targets."rails-${app.name}" = {
|
||||||
|
description = "${app.name} (Ruby on Rails)";
|
||||||
|
wantedBy = optional app.enable "multi-user.target";
|
||||||
|
};
|
||||||
|
|
||||||
|
services = appServices app;
|
||||||
|
timers = appTimers app;
|
||||||
|
};
|
||||||
|
|
||||||
|
in appSystemd
|
Loading…
Add table
Reference in a new issue