mirror of
https://github.com/vale981/phoebe
synced 2025-03-05 09:51:37 -05:00
Rails: Rework service execution so `nix-copy-closure' deployments work better
These changes allow service restarts to pick up a new environment (including the correct gemset) when restarted. Also, make migrations its own service that other services need to wait for.
This commit is contained in:
parent
66525b8df1
commit
3399464b17
7 changed files with 101 additions and 30 deletions
52
modules/services/web/rails/build-path.sh
Executable file
52
modules/services/web/rails/build-path.sh
Executable file
|
@ -0,0 +1,52 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Build a PATH variable given a nix-store path.
|
||||||
|
set -e
|
||||||
|
set -u
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
dirs=()
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
if [ $# -ne 1 ]; then
|
||||||
|
>&2 echo "ERROR: missing store path"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
function join() {
|
||||||
|
local sep=$1; shift
|
||||||
|
local IFS="$sep";
|
||||||
|
echo "$*";
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
function maybe_add_dir() {
|
||||||
|
local path=$1
|
||||||
|
|
||||||
|
if [ -n "$path" ] && [ -d "$path/bin" ]; then
|
||||||
|
dirs+=( "$path/bin" )
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
function main() {
|
||||||
|
local path=$1
|
||||||
|
path=$(realpath "$path")
|
||||||
|
|
||||||
|
maybe_add_dir "$path"
|
||||||
|
|
||||||
|
for drv in $(nix-store --query --references "$path"); do
|
||||||
|
maybe_add_dir "$drv"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "${#dirs[@]}" -gt 0 ]; then
|
||||||
|
echo "export PATH=$(join : "${dirs[@]}"):$PATH"
|
||||||
|
else
|
||||||
|
echo "export PATH=$PATH"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
main "$1"
|
|
@ -72,7 +72,6 @@ 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 = funcs.appPath app;
|
|
||||||
};
|
};
|
||||||
groups."rails-${app.name}" = {};
|
groups."rails-${app.name}" = {};
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,11 +12,6 @@ rec {
|
||||||
# Path to where the app is actually installed:
|
# Path to where the app is actually installed:
|
||||||
appLink = app: "${app.home}/package";
|
appLink = app: "${app.home}/package";
|
||||||
|
|
||||||
##############################################################################
|
|
||||||
# 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:
|
# All of the environment variables that a Rails app needs:
|
||||||
appEnv = app: {
|
appEnv = app: {
|
||||||
|
|
|
@ -86,6 +86,13 @@ let
|
||||||
default = false;
|
default = false;
|
||||||
description = "Is this the main Rails process?";
|
description = "Is this the main Rails process?";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
isMigration = mkOption {
|
||||||
|
internal = true;
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Is this the migration service?";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
# Rails user shell profile.
|
# Rails user shell profile.
|
||||||
|
if [ -e "$HOME/.path" ]; then
|
||||||
|
. "$HOME/.path"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -e "$HOME/.env" ]; then
|
if [ -e "$HOME/.env" ]; then
|
||||||
. "$HOME/.env"
|
. "$HOME/.env"
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
mkdir -p $out/bin
|
mkdir -p $out/bin
|
||||||
substituteAll ${./db-migrate.sh} $out/bin/db-migrate.sh
|
substituteAll ${./db-migrate.sh} $out/bin/db-migrate.sh
|
||||||
|
substituteAll ${./build-path.sh} $out/bin/build-path.sh
|
||||||
find $out/bin -type f -exec chmod 555 '{}' ';'
|
find $out/bin -type f -exec chmod 555 '{}' ';'
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
|
|
@ -23,39 +23,56 @@ let
|
||||||
name = "main";
|
name = "main";
|
||||||
schedule = null;
|
schedule = null;
|
||||||
isMain = true;
|
isMain = true;
|
||||||
|
isMigration = false;
|
||||||
|
|
||||||
script = ''
|
script = ''
|
||||||
puma -e ${app.railsEnv} -p ${toString app.port}
|
puma -e ${app.railsEnv} -p ${toString app.port}
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
# The database migration service:
|
||||||
|
migrationService = app: {
|
||||||
|
name = "migrations";
|
||||||
|
schedule = null;
|
||||||
|
isMain = false;
|
||||||
|
isMigration = true;
|
||||||
|
|
||||||
|
script = ''
|
||||||
|
${scripts.user}/bin/db-migrate.sh \
|
||||||
|
-r ${funcs.appLink app}/share/${app.name} \
|
||||||
|
-s ${app.home}/state
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# Generate a systemd service for a Ruby on Rails application:
|
# Generate a systemd service for a Ruby on Rails application:
|
||||||
appService = app: service: {
|
appService = app: service: {
|
||||||
"rails-${app.name}-${service.name}" = {
|
"rails-${app.name}-${service.name}" = {
|
||||||
description = "${app.name} (Ruby on Rails) ${service.name}";
|
description = "${app.name} (Ruby on Rails) ${service.name}";
|
||||||
path = funcs.appPath app;
|
path = with pkgs; [ coreutils nix ];
|
||||||
environment = funcs.appEnv app;
|
environment = funcs.appEnv app;
|
||||||
|
|
||||||
# Only start this service if it isn't scheduled by a timer.
|
# Only start this service if it isn't scheduled by a timer.
|
||||||
partOf = optional (service.schedule == null) "rails-${app.name}.target";
|
partOf = optional (service.schedule == null) "rails-${app.name}.target";
|
||||||
wantedBy = optional (service.schedule == null) "rails-${app.name}.target";
|
wantedBy = optional (service.schedule == null) "rails-${app.name}.target";
|
||||||
|
|
||||||
wants =
|
wants = plib.keyService app.database.passwordFile
|
||||||
plib.keyService app.database.passwordFile ++
|
++ plib.keyService app.sourcedFile
|
||||||
plib.keyService app.sourcedFile ++
|
++ app.afterServices
|
||||||
app.afterServices;
|
++ optional (!service.isMigration && app.database.migrate) "rails-${app.name}-migrations"
|
||||||
|
++ optional (!service.isMain && !service.isMigration) "rails-${app.name}-main";
|
||||||
|
|
||||||
after =
|
after = [ "network.target" ]
|
||||||
[ "network.target" ] ++
|
++ optional localpg "postgresql.service"
|
||||||
optional localpg "postgresql.service" ++
|
++ optional localpg "postgres-account-manager.service"
|
||||||
optional localpg "pg-accounts.service" ++
|
++ plib.keyService app.database.passwordFile
|
||||||
optional (!service.isMain) "rails-${app.name}-main" ++
|
++ plib.keyService app.sourcedFile
|
||||||
plib.keyService app.database.passwordFile ++
|
++ app.afterServices
|
||||||
plib.keyService app.sourcedFile ++
|
++ optional (!service.isMigration && app.database.migrate) "rails-${app.name}-migrations"
|
||||||
app.afterServices;
|
++ optional (!service.isMain && !service.isMigration) "rails-${app.name}-main";
|
||||||
|
|
||||||
preStart = optionalString service.isMain ''
|
preStart = optionalString (service.isMain || service.isMigration) ''
|
||||||
# Link the package into the application's home directory:
|
# Link the package into the application's home directory:
|
||||||
if [ ! -e "${funcs.appLink app}" ] || [ "${toString app.deployedExternally}" -ne 1 ]; then
|
if [ ! -e "${funcs.appLink app}" ] || [ "${toString app.deployedExternally}" -ne 1 ]; then
|
||||||
ln -nfs "${app.package}" "${funcs.appLink app}"
|
ln -nfs "${app.package}" "${funcs.appLink app}"
|
||||||
|
@ -73,6 +90,7 @@ let
|
||||||
mkdir -p ${app.home}/home
|
mkdir -p ${app.home}/home
|
||||||
ln -nfs ${funcs.appLink app}/share/${app.name} ${app.home}/home/app
|
ln -nfs ${funcs.appLink app}/share/${app.name} ${app.home}/home/app
|
||||||
ln -nfs ${plib.attrsToShellExports "rails-${app.name}-env" (funcs.appEnv app)} ${app.home}/home/.env
|
ln -nfs ${plib.attrsToShellExports "rails-${app.name}-env" (funcs.appEnv app)} ${app.home}/home/.env
|
||||||
|
echo 'eval $(${scripts.user}/bin/build-path.sh "${funcs.appLink app}")' > ${app.home}/home/.path
|
||||||
cp ${./profile.sh} ${app.home}/home/.profile
|
cp ${./profile.sh} ${app.home}/home/.profile
|
||||||
chmod 0700 ${app.home}/home/.profile
|
chmod 0700 ${app.home}/home/.profile
|
||||||
|
|
||||||
|
@ -85,25 +103,19 @@ let
|
||||||
chown -R rails-${app.name}:rails-${app.name} ${app.home}
|
chown -R rails-${app.name}:rails-${app.name} ${app.home}
|
||||||
chmod go+rx $(dirname "${app.home}") "${app.home}"
|
chmod go+rx $(dirname "${app.home}") "${app.home}"
|
||||||
chmod u+w ${app.home}/db/schema.rb
|
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.user}/bin/db-migrate.sh \
|
|
||||||
-r ${funcs.appLink app}/share/${app.name} \
|
|
||||||
-s ${app.home}/state
|
|
||||||
'';
|
'';
|
||||||
|
|
||||||
script = ''
|
script = ''
|
||||||
${optionalString (app.sourcedFile != null) ". ${app.home}/state/sourcedFile.sh"}
|
${optionalString (app.sourcedFile != null) ". ${app.home}/state/sourcedFile.sh"}
|
||||||
|
eval $(${scripts.user}/bin/build-path.sh "${funcs.appLink app}")
|
||||||
${service.script}
|
${service.script}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
WorkingDirectory = "-${funcs.appLink app}/share/${app.name}";
|
WorkingDirectory = "-${funcs.appLink app}/share/${app.name}";
|
||||||
Restart = "on-failure";
|
Type = if service.isMigration then "oneshot" else "simple";
|
||||||
|
Restart = if service.isMigration then "no" else "on-failure";
|
||||||
TimeoutSec = "infinity"; # FIXME: what's a reasonable amount of time?
|
TimeoutSec = "infinity"; # FIXME: what's a reasonable amount of time?
|
||||||
Type = "simple";
|
|
||||||
PermissionsStartOnly = true;
|
PermissionsStartOnly = true;
|
||||||
User = "rails-${app.name}";
|
User = "rails-${app.name}";
|
||||||
Group = "rails-${app.name}";
|
Group = "rails-${app.name}";
|
||||||
|
@ -128,7 +140,9 @@ let
|
||||||
# systemd services.
|
# systemd services.
|
||||||
appServices = app:
|
appServices = app:
|
||||||
foldr (service: set: recursiveUpdate set (appService app service)) {}
|
foldr (service: set: recursiveUpdate set (appService app service)) {}
|
||||||
( [(mainService app)] ++ attrValues app.services );
|
( singleton (mainService app)
|
||||||
|
++ optional app.database.migrate (migrationService app)
|
||||||
|
++ attrValues app.services );
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
# Collect all services and turn them into systemd timers:
|
# Collect all services and turn them into systemd timers:
|
||||||
|
|
Loading…
Add table
Reference in a new issue